Skip to content

Address audit findings (5.3 diff audit) #5584

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Mar 19, 2025
4 changes: 4 additions & 0 deletions contracts/access/manager/AuthorityUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ library AuthorityUtils {
if staticcall(gas(), authority, add(data, 0x20), mload(data), 0x00, 0x40) {
immediate := mload(0x00)
delay := mload(0x20)

// If delay does not fit in a uint32, return 0 (no delay)
// equivalent to: if gt(delay, 0xFFFFFFFF) { delay := 0 }
delay := mul(delay, iszero(shr(32, delay)))
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/draft-IERC6909.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ interface IERC6909 is IERC165 {
function isOperator(address owner, address spender) external view returns (bool);

/**
* @dev Sets an approval to `spender` for `amount` tokens of type `id` from the caller's tokens.
* @dev Sets an approval to `spender` for `amount` tokens of type `id` from the caller's tokens. An `amount` of
* `type(uint256).max` signifies an unlimited approval.
*
* Must return true.
*/
Expand Down
10 changes: 5 additions & 5 deletions contracts/mocks/AuthorityMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ contract NotAuthorityMock is IAuthority {
}

contract AuthorityNoDelayMock is IAuthority {
bool _immediate;
bool private _immediate;

function canCall(
address /* caller */,
Expand All @@ -28,22 +28,22 @@ contract AuthorityNoDelayMock is IAuthority {
}

contract AuthorityDelayMock {
bool _immediate;
uint32 _delay;
bool private _immediate;
uint256 private _delay;

function canCall(
address /* caller */,
address /* target */,
bytes4 /* selector */
) external view returns (bool immediate, uint32 delay) {
) external view returns (bool immediate, uint256 delay) {
return (_immediate, _delay);
}

function _setImmediate(bool immediate) external {
_immediate = immediate;
}

function _setDelay(uint32 delay) external {
function _setDelay(uint256 delay) external {
_delay = delay;
}
}
Expand Down
12 changes: 7 additions & 5 deletions contracts/token/ERC6909/draft-ERC6909.sol
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ contract ERC6909 is Context, ERC165, IERC6909 {

/**
* @dev Creates `amount` of token `id` and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
Expand All @@ -93,10 +93,9 @@ contract ERC6909 is Context, ERC165, IERC6909 {
}

/**
* @dev Moves `amount` of token `id` from `from` to `to` without checking for approvals.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
* @dev Moves `amount` of token `id` from `from` to `to` without checking for approvals. This function verifies
* that neither the sender nor the receiver are address(0), which means it cannot mint or burn tokens.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event.
*
Expand Down Expand Up @@ -166,6 +165,9 @@ contract ERC6909 is Context, ERC165, IERC6909 {
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Note: Setting `amount` to `type(uint256).max` is equivalent to setting an "infinite" allowance that won't
* be reduced when used.
*/
function _approve(address owner, address spender, uint256 id, uint256 amount) internal virtual {
if (owner == address(0)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ contract ERC6909TokenSupply is ERC6909, IERC6909TokenSupply {
}
if (to == address(0)) {
unchecked {
// amount <= _balances[id][from] <= _totalSupplies[id]
// amount <= _balances[from][id] <= _totalSupplies[id]
_totalSupplies[id] -= amount;
}
}
Expand Down
4 changes: 4 additions & 0 deletions contracts/utils/Strings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,10 @@ library Strings {
* @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.
*
* WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.
*
* NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of
* RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). Javascript's `JSON.parse` does recover escaped unicode
* characters that are not in this range, but other tooling may provide different results.
*/
function escapeJSON(string memory input) internal pure returns (string memory) {
bytes memory buffer = bytes(input);
Expand Down
2 changes: 1 addition & 1 deletion contracts/utils/structs/MerkleTree.sol
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ library MerkleTree {
* root (before the update) and "new" root (after the update). The caller must verify that the reconstructed old
* root is the last known one.
*
* The `proof` must be an up-to-date inclusion proof for the leaf being update. This means that this function is
* The `proof` must be an up-to-date inclusion proof for the leaf being updated. This means that this function is
* vulnerable to front-running. Any {push} or {update} operation (that changes the root of the tree) would render
* all "in flight" updates invalid.
*
Expand Down
12 changes: 11 additions & 1 deletion test/access/manager/AuthorityUtils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const { ethers } = require('hardhat');
const { expect } = require('chai');
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');

const { MAX_UINT32, MAX_UINT64 } = require('../../helpers/constants');

async function fixture() {
const [user, other] = await ethers.getSigners();

Expand Down Expand Up @@ -70,7 +72,7 @@ describe('AuthorityUtils', function () {
});

for (const immediate of [true, false]) {
for (const delay of [0n, 42n]) {
for (const delay of [0n, 42n, MAX_UINT32]) {
it(`returns (immediate=${immediate}, delay=${delay})`, async function () {
await this.authority._setImmediate(immediate);
await this.authority._setDelay(delay);
Expand All @@ -80,6 +82,14 @@ describe('AuthorityUtils', function () {
});
}
}

it('out of bound delay', async function () {
await this.authority._setImmediate(false);
await this.authority._setDelay(MAX_UINT64); // bigger than the expected uint32
const result = await this.mock.$canCallWithDelay(this.authority, this.user, this.other, '0x12345678');
expect(result.immediate).to.equal(false);
expect(result.delay).to.equal(0n);
});
});

describe('when authority replies with empty data', function () {
Expand Down
1 change: 1 addition & 0 deletions test/helpers/constants.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
MAX_UINT32: 2n ** 32n - 1n,
MAX_UINT48: 2n ** 48n - 1n,
MAX_UINT64: 2n ** 64n - 1n,
};
Loading