Reach out for an audit or to learn more about Macro
or Message on Telegram

thirdweb A-7

Security Audit

December 21st, 2022

Version 1.0.0

Presented by 0xMacro

Table of Contents

Introduction

This document includes the results of the security audit for thirdweb's smart contract code as found in the section titled ‘Source Code’. The security audit was performed by the Macro security team from November 28, 2022 to December 7, 2022.

The purpose of this audit is to review the source code of certain thirdweb Solidity contracts, and provide feedback on the design, architecture, and quality of the source code with an emphasis on validating the correctness and security of the software in its entirety.

Disclaimer: While Macro’s review is comprehensive and has surfaced some changes that should be made to the source code, this audit should not solely be relied upon for security, as no single audit is guaranteed to catch all possible bugs.

Overall Assessment

The following is an aggregation of issues found by the Macro Audit team:

Severity Count Acknowledged Won't Do Addressed
Critical 1 - - 1
High 2 - - 2
Medium 3 - - 3
Low 1 - - 1
Code Quality 8 1 1 6
Gas Optimization 3 - 1 2

thirdweb was quick to respond to these issues.

Specification

Our understanding of the specification was based on the following sources:

Source Code

The following source code was reviewed during the audit:

Specifically, we audited the following contracts as part of TokenStake contract audit:

Source Code SHA256
contracts/openzeppelin-presets/metatx/ERC2771ContextUpgradeable.sol

4ef0ce1601048c10a4b0fdc3247062be8f1a9ca0441c862ddfadc16251a31edb

contracts/lib/CurrencyTransferLib.sol

ab7e40d1b333d675e23d9d4a4c70836c508b2e8b890cf1c6f3dc554424d1215d

contracts/interfaces/IWETH.sol

839869bd411a4e68c9a59d2a0c394a087641eeeadeda4956a255dc3179110cc3

contracts/openzeppelin-presets/token/ERC20/utils/SafeERC20.sol

569cd0b266ff404aeac1a4266a1535121e47907ef1dcea2d55f4c036a11f758e

contracts/eip/interface/IERC20.sol

f26adf2080a8611b95fb06653a7476aade9dfae3fcc36b212aee46e0aa914f7f

contracts/lib/TWAddress.sol

e63c27c95600ee1886258f2e588bc6e00a8b6224bfba341c1b05a78baa3755e1

contracts/extension/ContractMetadata.sol

e2b7ba9418f86c2049b1ad85faae6d019bcf432307b61f36eeb025d3be77c615

contracts/extension/interface/IContractMetadata.sol

f0b7ac93fba3fbb8a71bd76da822cceec0fa86b20418835a228c67d06176eaec

contracts/extension/PermissionsEnumerable.sol

e11e8f40eb775c0ccc34b746b03a08e584f626b94fdde1396f538c3a76c487d4

contracts/extension/interface/IPermissionsEnumerable.sol

b4958ce026d4c0c93dae5e671aabed246531c448e1e081d0ba6dc590c3377393

contracts/extension/interface/IPermissions.sol

333d596baf00c08da55bc1671da3f5df65c4a1d9e8d5639e910d1c23ffb7f980

contracts/extension/Permissions.sol

645d29f8042f1318e9b307f5e85e4408bde24ee6fcb81202a82e23cd8b549587

contracts/extension/interface/IPermissions.sol

333d596baf00c08da55bc1671da3f5df65c4a1d9e8d5639e910d1c23ffb7f980

contracts/lib/TWStrings.sol

d1fa327e26529fe1048a230a9fdaa183da21f9418729b665e0d57f68f136de0b

contracts/extension/Staking20Upgradeable.sol

16bc29a644b43fb9d1c29fd11d06e502d351934e51c1ae127e113dac8c3bf713

contracts/extension/interface/IStaking20.sol

3bf8b010deb83bc35f3fd2c1a3fef2dd0cc679108cfcd19003dceb1dd9619259

We audited the following contracts as part of NFTStake contract audit:

Source Code SHA256
contracts/openzeppelin-presets/metatx/ERC2771ContextUpgradeable.sol

4ef0ce1601048c10a4b0fdc3247062be8f1a9ca0441c862ddfadc16251a31edb

contracts/lib/CurrencyTransferLib.sol

ab7e40d1b333d675e23d9d4a4c70836c508b2e8b890cf1c6f3dc554424d1215d

contracts/interfaces/IWETH.sol

839869bd411a4e68c9a59d2a0c394a087641eeeadeda4956a255dc3179110cc3

contracts/openzeppelin-presets/token/ERC20/utils/SafeERC20.sol

569cd0b266ff404aeac1a4266a1535121e47907ef1dcea2d55f4c036a11f758e

contracts/eip/interface/IERC20.sol

f26adf2080a8611b95fb06653a7476aade9dfae3fcc36b212aee46e0aa914f7f

contracts/lib/TWAddress.sol

e63c27c95600ee1886258f2e588bc6e00a8b6224bfba341c1b05a78baa3755e1

contracts/extension/ContractMetadata.sol

e2b7ba9418f86c2049b1ad85faae6d019bcf432307b61f36eeb025d3be77c615

contracts/extension/interface/IContractMetadata.sol

f0b7ac93fba3fbb8a71bd76da822cceec0fa86b20418835a228c67d06176eaec

contracts/extension/PermissionsEnumerable.sol

e11e8f40eb775c0ccc34b746b03a08e584f626b94fdde1396f538c3a76c487d4

contracts/extension/interface/IPermissionsEnumerable.sol

b4958ce026d4c0c93dae5e671aabed246531c448e1e081d0ba6dc590c3377393

contracts/extension/interface/IPermissions.sol

333d596baf00c08da55bc1671da3f5df65c4a1d9e8d5639e910d1c23ffb7f980

contracts/extension/Permissions.sol

645d29f8042f1318e9b307f5e85e4408bde24ee6fcb81202a82e23cd8b549587

contracts/extension/interface/IPermissions.sol

333d596baf00c08da55bc1671da3f5df65c4a1d9e8d5639e910d1c23ffb7f980

contracts/lib/TWStrings.sol

d1fa327e26529fe1048a230a9fdaa183da21f9418729b665e0d57f68f136de0b

contracts/extension/Staking721Upgradeable.sol

e5790681a79244dc79ac78554ed3802d7dd5e8e6df6a961eba17e76f4b975550

contracts/extension/interface/IStaking721.sol

9575a51df8e3c950d53ab4916ef50ad60698f59267484cc77cd98841e247629d

We audited the following contracts as part of EditionStake contract audit:

Source Code SHA256
contracts/openzeppelin-presets/metatx/ERC2771ContextUpgradeable.sol

4ef0ce1601048c10a4b0fdc3247062be8f1a9ca0441c862ddfadc16251a31edb

contracts/lib/CurrencyTransferLib.sol

ab7e40d1b333d675e23d9d4a4c70836c508b2e8b890cf1c6f3dc554424d1215d

contracts/interfaces/IWETH.sol

839869bd411a4e68c9a59d2a0c394a087641eeeadeda4956a255dc3179110cc3

contracts/openzeppelin-presets/token/ERC20/utils/SafeERC20.sol

569cd0b266ff404aeac1a4266a1535121e47907ef1dcea2d55f4c036a11f758e

contracts/eip/interface/IERC20.sol

f26adf2080a8611b95fb06653a7476aade9dfae3fcc36b212aee46e0aa914f7f

contracts/lib/TWAddress.sol

e63c27c95600ee1886258f2e588bc6e00a8b6224bfba341c1b05a78baa3755e1

contracts/extension/ContractMetadata.sol

e2b7ba9418f86c2049b1ad85faae6d019bcf432307b61f36eeb025d3be77c615

contracts/extension/interface/IContractMetadata.sol

f0b7ac93fba3fbb8a71bd76da822cceec0fa86b20418835a228c67d06176eaec

contracts/extension/PermissionsEnumerable.sol

e11e8f40eb775c0ccc34b746b03a08e584f626b94fdde1396f538c3a76c487d4

contracts/extension/interface/IPermissionsEnumerable.sol

b4958ce026d4c0c93dae5e671aabed246531c448e1e081d0ba6dc590c3377393

contracts/extension/interface/IPermissions.sol

333d596baf00c08da55bc1671da3f5df65c4a1d9e8d5639e910d1c23ffb7f980

contracts/extension/Permissions.sol

645d29f8042f1318e9b307f5e85e4408bde24ee6fcb81202a82e23cd8b549587

contracts/extension/interface/IPermissions.sol

333d596baf00c08da55bc1671da3f5df65c4a1d9e8d5639e910d1c23ffb7f980

contracts/lib/TWStrings.sol

d1fa327e26529fe1048a230a9fdaa183da21f9418729b665e0d57f68f136de0b

contracts/extension/Staking1155Upgradeable.sol

a7c9d1a429a83e8dcac52d57cd30f41fde67562620385f251c3f764ce9c4ad39

contracts/eip/interface/IERC1155.sol

5e39ff49f56e075e5111dd66a8d83a153aed331ef5af8762f491247295cd542a

contracts/extension/interface/IStaking1155.sol

fa855b955f07067c48640f60f887f5fff038408b97f797c56ef3b982ca6cb763

Note: This document contains an audit solely of the Solidity contracts listed above. Specifically, the audit pertains only to the contracts themselves, and does not pertain to any other programs or scripts, including deployment scripts.

Issue Descriptions and Recommendations

Click on an issue to jump to it, or scroll down to see them all.

Security Level Reference

We quantify issues in three parts:

  1. The high/medium/low/spec-breaking impact of the issue:
    • How bad things can get (for a vulnerability)
    • The significance of an improvement (for a code quality issue)
    • The amount of gas saved (for a gas optimization)
  2. The high/medium/low likelihood of the issue:
    • How likely is the issue to occur (for a vulnerability)
  3. The overall critical/high/medium/low severity of the issue.

This third part – the severity level – is a summary of how much consideration the client should give to fixing the issue. We assign severity according to the table of guidelines below:

Severity Description
(C-x)
Critical

We recommend the client must fix the issue, no matter what, because not fixing would mean significant funds/assets WILL be lost.

(H-x)
High

We recommend the client must address the issue, no matter what, because not fixing would be very bad, or some funds/assets will be lost, or the code’s behavior is against the provided spec.

(M-x)
Medium

We recommend the client to seriously consider fixing the issue, as the implications of not fixing the issue are severe enough to impact the project significantly, albiet not in an existential manner.

(L-x)
Low

The risk is small, unlikely, or may not relevant to the project in a meaningful way.

Whether or not the project wants to develop a fix is up to the goals and needs of the project.

(Q-x)
Code Quality

The issue identified does not pose any obvious risk, but fixing could improve overall code quality, on-chain composability, developer ergonomics, or even certain aspects of protocol design.

(I-x)
Informational

Warnings and things to keep in mind when operating the protocol. No immediate action required.

(G-x)
Gas Optimizations

The presented optimization suggestion would save an amount of gas significant enough, in our opinion, to be worth the development cost of implementing it.

Issue Details

C-1

Contract admins can lock staked tokens in the contract

Topic
Locked Assets
Status
Impact
High
Likelihood
High

In all three staking contracts, administrator role members (DEFAULT_ADMIN_ROLE) have permission to set and change the staking parameters rewardsPerUnitTime and timeUnit.

By setting timeUnit = 0, the _calculateRewards() logical math would try to execute a division by zero and, by setting rewardsPerUnitTime to a high value such as type(uint256).max, the operation will result in an overflow. Both of these parameter configurations will make every attempt of the user to withdraw, stake new tokens, or claim rewards revert, essentially breaking the entire contract and locking the existent tokens in the staking contract.

In the latest case, the value of rewardsPerUnitTime could be less than the max value, depending on the time lapsed and the amount staked.

function _calculateRewards(address _staker) internal view virtual returns (uint256 _rewards) {
  Staker memory staker = stakers[_staker];
  _rewards = ((((block.timestamp - staker.timeOfLastUpdate) * staker.amountStaked) * rewardsPerUnitTime) / timeUnit);
}

These conditions are irreversible: all subsequent parameter changes will revert due to the reward update logic.

Remediations to Consider

For timeUnit= 0:

  • Requiring the timeUnit value to be always higher than zero.

For rewardsPerUnitTime:

  • Executing an overflow check and ignoring the rewards result if overflow.
  • Allowing users to withdraw no matter the staking conditions.
H-1

TokenStake.sol rewards can be over- or under-awarded when the staking and reward tokens have different decimals

Topic
Incorrect Rewards
Status
Impact
Spec Breaking
Likelihood
High

The documentation for setRewardRatio() explains:

Set rewards per unit of time. Interpreted as (numerator/denominator) rewards per second/per day/etc based on time-unit. For e.g., ratio of 1/20 would mean 1 reward token for every 20 tokens staked.

This is correct in cases where the staking and reward tokens have identical decimal values. Otherwise, rewards will be over- or under-awarded by orders of magnitude equivalent to the decimal difference.

Remediations to Consider

Consider accounting for both token decimals in reward calculations.

H-2

TokenStake.sol: Tokens with a tax on transfer will account for inaccurate amounts

Topic
Incorrect Rewards / Asset Loss
Status
Impact
Spec Breaking
Likelihood
High

If a token with on-transfer fees is set up as the staked token, the stake() function will store an inaccurate amount. As a result:

  • Rewards will be calculated from higher staked amounts than actually present in the contract.
  • Returned amount from getStakeInfo() will not reflect the actual balances of the contract and staked amount.
  • Withdrawals of the full staked amount will transfer non-corresponded tokens from other stakers.
  • For a TokenStake with a unique stake, calling withdraw with the amount returned from getStakeInfo() will revert. To claim rewards the user would have to manually check the balances in the contract and use the actually transferred tokens.

Remediations to Consider

Consider, in the _stake() logic, using the balance of the contract before and after transferring the tokens to account for the corresponding amounts per staker.

M-1

Block gas limit can be exceeded during setTimeUnit() and setRewardsPerUnit() when staker count grows

Topic
Gas Limit
Status
Impact
Medium
Likelihood
Low
commits: 4f571568a53c7e9403269a3f32cf3965faf36d84, 13a8af2e19f18a1d70d306a9910c57ce7d8cc8cd

This applies to all three staking implementations. The admin will no longer be able to call these functions, preventing the admin from increasing / decreasing / halting staking reward configuration. The admin may be forced to continue to support the present configurations, or halt funding entirely: in this case users will have no on-chain signal that rewards are no longer supported. This can increase the likelihood of cases where user unclaimedRewards exceeds budgeted supply, resulting in reward loss.

This occurs because these methods calculate and update rewards for every staker in stakerArray. The gas cost will exceed the current block gas limit of 30M when staker counts climb past:

  • ~1,000 for TokenStake.sol
  • ~1,000 for NFTStake.sol
  • ~725 for EditionStake.sol

Note that the transaction gas cost may become prohibitively high for admin users well before the block gas limit is reached, with fewer stakers.

Remediations to Consider

Consider altering the staking and rewards logic to not require state modification for every staker. One such reference implementation is the Synthetix StakingRewards.sol contract.

Alternatively, consider including these limitations in developer documentation, potentially also noting increasing gas costs as these limits are approached.

M-2

ERC721 and ERC1155 tokens safe-transferred directly to contract will be locked and unrecoverable

Topic
Locked Assets
Status
Impact
High
Likelihood
Medium

Once transferred, the token cannot be recovered via withdraw(). This occurs because NFTStake.sol supports onERC721Received() which allows any safe transfer to succeed, but only correctly accounts for the staking if stake() is used. Similar conditions exist for EditionStake.sol.

This finding is not concerned with non-safe transfers of ERC721 tokens: these cannot be prevented, and are generally discouraged when sending tokens to a new address.

Remediations to Consider

Consider implementing a state variable toggle to allow safe transfers only from within stake(). A generalized example:

contract ... {
    uint8 private isStaking = 1;

    ...

    function _stake(...) ... {
    ...
        isStaking = 2;
        IERCx(...).safeTransferFrom(...);
        isStaking = 1;
    ...
    }

    function onERCxReceived(...) {
        require(isStaking == 2);
        ...
    }
}

Note that some method locations may need to be refactored to pursue this approach.

M-3

TokenStake.sol: Double entry-point ERC20 tokens could be drained from the staking contract

Topic
ERC20 Exploit
Status
Impact
High
Likelihood
Low

A double entry-point ERC20 Token is an ERC20 with a proxy pattern that allows users (and contracts) to interact directly with the target contract, skipping the proxy. Since it is possible to interact with both the proxy and the target directly, the token has two entry points.

By setting a TokenStake contract with both these addresses as reward and token, admins could drain rewards and staked tokens equally.

This issue was presented a few months ago with every Synthetix asset and the TUSD contract and was later patched after realizing several interactions with DeFi contracts caused vulnerabilities. Naturally, this does not mean that this vulnerability can never arise again, as there might be more tokens out there with multiple entry points.

Remediations to Consider

Consider adding a state to track the deposited rewards with a pull mechanism and add or subtract when adding/claiming rewards to keep rewards and staked amounts independent.

L-1

Incorrect ERC165 implementation for NFTStake and EditionStake

Topic
ERC165 Spec
Status
Impact
Spec Breaking
Likelihood
Low

ERC165 is not correctly implemented, which will yield false when these contracts are queried for the interfaces they are intended to support. This may block some token transfers.

Remediations to Consider

Per the ERC165 spec, consider updating supportsInterface() to also return true when interfaceId is 0x01ffc9a7.

Q-1

NFT tokens do not transfer via safeTransferFrom() during stake()

Topic
ERC721
Status
Quality Impact
Low
commits: dc44c159e933f974b248824e343e2d26d197b203, a10df026b20c4934d481fb2b1a55853224d28e22

Staking721Upgradeable.sol does not use safeTransferFrom() within stake(), rendering NFTStake.sol’s onERC721Received() unused.

Remediations to Consider

Consider updating stake() logic to call safeTransferFrom().

Q-2

Normalize support for ERC2771 trusted forwarder

Topic
ERC2771
Status
Acknowledged
Quality Impact
Low
A partial fix was submitted which did not include an update for Permissions.sol. The Response field below pertains to the code at this state.

The prebuilt contracts NFTStake.sol, EditionStake.sol, TokenStake.sol partially support trusted forwarders via _msgSender(), but their dependencies do not (e.g. Permissions.sol, Staking721Upgradeable.sol, etc). Notably, attempts to call Permissions.sol methods: grantRole(), revokeRole(), renounceRole(), or the onlyRole() modifier on NFTStake, EditionStake, TokenStake (or contracts derived from them) via trusted forwarder will apply the changes to the trusted forwarder address, not the intended transaction originator. This could have unintended consequences, such as inadvertently granting admin privileges to all transactions sent through a forwarder.

Consider implementing support for _msgSender() in all dependency contracts. OpenZeppelin does this by adding Context as a common base class. We recommend something similar. If this is not possible in the short term, we recommend pulling ERC2771 support out from these implementations of TokenStake, NFTStake, EditionStake until a more wholistic solution is adopted.

Response by thirdweb

For Q-2, we want to go ahead with the current implementation. Although permissions doesn't support the gasless setup, those actions will be limited to admin, or a few wallets. The purpose right now was to enable gasless for staking specific txns, for majority of the users. Admin related limitations can be documented for now. We'll update Permissions and other extensions with gasless setup in later releases, as these are extended in other contracts as well.

Q-3

Reentrancy init called twice

Topic
Code Duplication
Status
Quality Impact
Low

__ReentrancyGuard_init() is being called both in the parent (Staking721Upgradeable.sol, Staking1155Upgradeable.sol, and Staking20Upgradeable.sol) and child (NFTStake.sol, EditionStake.sol, and TokenStake.sol) contract initialize() functions. Consider removing this initialization call from the NFTStake.sol, EditionStake.sol, and TokenStake.sol contracts.

Q-4

Duplicated logic is prone to introducing defects during customization

Topic
Extensibility
Status
Quality Impact
Low

Staking721Upgradeable.sol — and the ERC20 and ERC1155 counterparts — implement the following identical logic for updating a single user’s unclaimed rewards within both _updateUnclaimedRewardsForStake() and _updateUnclaimedRewardsForAll() :

uint256 rewards = _calculateRewards(_staker);
stakers[_staker].unclaimedRewards += rewards;
stakers[_staker].timeOfLastUpdate = block.timestamp;

A user who customizes _updateUnclaimedRewardsForStake() must also remember to customize _updateUnclaimedRewardsForAll(), otherwise unexpected defects may be introduced.

Rather than duplicating logic, consider calling _updateUnclaimedRewardsForStake() from _updateUnclaimedRewardsForAll().

Q-5

unitTime and rewardsPerUnitTime setter functions don’t check for new input data

Topic
Validation
Status
Quality Impact
Low

Admins can mistakenly call these functions with the same value already set, unnecessarily incurring considerably high gas costs, depending on the number of stakers. To avoid this, consider requiring different input data from the already stored values.

Q-6

getStakeInfo should be external

Topic
Extensibility
Status
Quality Impact
Low

This view function performs multiple loops on unbounded arrays. For an external view function it won’t be an issue, but since the function visibility is public it could suggest the use of it from inside the contract. Consider updating this method to be external.

Q-7

Missing reward balance information

Topic
User Experience
Status
Quality Impact
Low

Staking contracts provide no ready insight into reward balances or withdrawals. Users will have to locate and inspect transaction activity of the reward token contract to confirm availability of rewards.

Consider increasing the availability of reward metadata on-contract. For example:

  • Adding a view method to return present reward balance
  • Emitting an event when withdrawRewardTokens() is called.
Q-8

Staking1155Upgradeable claimRewards function for each token id

Topic
User Experience
Status
Wont Do
Quality Impact
Low

For ERC1155 staking contracts (EditionStake.sol and Staking1155Upgradeable.sol), the claimRewards() function differs from the other two staking contracts; this one is done for each tokenId individually, different from claiming all rewards with one call the other contracts have. This implies every user would need to execute one transaction for each tokenId staked, causing high amounts of gas and actions for a non-fungible ERC1155 design.

EditionStake.sol supports multi-call, which might be a solution when using that contract specifically, but not for contracts inheriting from Staking1155Upgradeable.

Consider having a function for claiming all rewards for the total amount of tokens staked and one for individual claims per token ID if desired.

G-1

Halt array iteration after staker removed during withdraw()

Topic
Gas Optimization
Status
Gas Savings
Low

See Staking721Upgradeable.sol:

for (uint256 i = 0; i < stakersArray.length; ++i) {
    if (stakersArray[i] == msg.sender) {
        stakersArray[i] = stakersArray[stakersArray.length - 1];
        stakersArray.pop();
    }
}

This will cycle over the entire stakerArray, even after the intended operation is completed. Consider calling break at the end of the if block, as is implemented in the other two staking contracts (Staking1155Upgreadable.sol and Staking20Upgreadable.sol).

G-2

Loop reading from storage array length

Topic
Gas Optimization
Status
Gas Savings
Low

In Staking721Upgreadable.sol, line 213, we have the following logic inside the _withdraw() function:

for (uint256 i = 0; i < stakersArray.length; ++i) {
    if (stakersArray[i] == msg.sender) {
        stakersArray[i] = stakersArray[stakersArray.length - 1];
        stakersArray.pop();
    }
}

Both stakersArray.length and starkersArray[i] inside the if statement can be optimized by storing them in memory, as is currently implemented in the other two staking contracts (Staking1155Upgreadable.sol and Staking20Upgreadable.sol).

Another optimization is to use the stakersArray[i] = stakersArray[stakersArray.length - 1] from memory too, this can be added to all three contracts.

G-3

High gas when calling both setTimeUnit() and setRewardsPerUnitTime()

Topic
Gas Optimization
Status
Wont Do
Gas Savings
Low
The originally reported issue was partially remediated with the M-1 fixes. A remaining gas issue was reported during fixes review: "Admins that wish to change both settings at once will end up with two new entries in the conditions mapping. This incurs extra storage writes for the admin, and incurs extra storage reads for every user during stake/withdraw/claimRewards. If both updates land in the same block, it’s just a gas concern as noted. Otherwise, the new condition for the first updated setting will have a non-zero duration, causing rewards to be accumulated on an unintended (incomplete) staking configuration." The Response section below pertains to this remaining gas issue.

Admins that want to change both values will need to call each method separately, which will both invoke _updateUnclaimedRewards(), which can become expensive as the number of stakers increase.

Consider implementing an additional method to update both settings at once, requiring only a single call to _updateUnclaimedRewards().

Response by thirdweb

For G-3, we're not making any changes right now. Relying on multicall for updating both conditions at once.

Disclaimer

Macro makes no warranties, either express, implied, statutory, or otherwise, with respect to the services or deliverables provided in this report, and Macro specifically disclaims all implied warranties of merchantability, fitness for a particular purpose, noninfringement and those arising from a course of dealing, usage or trade with respect thereto, and all such warranties are hereby excluded to the fullest extent permitted by law.

Macro will not be liable for any lost profits, business, contracts, revenue, goodwill, production, anticipated savings, loss of data, or costs of procurement of substitute goods or services or for any claim or demand by any other party. In no event will Macro be liable for consequential, incidental, special, indirect, or exemplary damages arising out of this agreement or any work statement, however caused and (to the fullest extent permitted by law) under any theory of liability (including negligence), even if Macro has been advised of the possibility of such damages.

The scope of this report and review is limited to a review of only the code presented by the thirdweb team and only the source code Macro notes as being within the scope of Macro’s review within this report. This report does not include an audit of the deployment scripts used to deploy the Solidity contracts in the repository corresponding to this audit. Specifically, for the avoidance of doubt, this report does not constitute investment advice, is not intended to be relied upon as investment advice, is not an endorsement of this project or team, and it is not a guarantee as to the absolute security of the project. In this report you may through hypertext or other computer links, gain access to websites operated by persons other than Macro. Such hyperlinks are provided for your reference and convenience only, and are the exclusive responsibility of such websites’ owners. You agree that Macro is not responsible for the content or operation of such websites, and that Macro shall have no liability to your or any other person or entity for the use of third party websites. Macro assumes no responsibility for the use of third party software and shall have no liability whatsoever to any person or entity for the accuracy or completeness of any outcome generated by such software.