019d5666-7216-7238-b7b1-e53620c8a60e
Open this run to answer one question: is this real attacker-relevant behavior with real impact, or just normal contract plumbing?
| ID | Type | Status | Attempts | Heartbeat |
|---|---|---|---|---|
| 42862 | artifact.fetch | complete | 1 / 3 | 1 week ago |
| 42863 | proxy.resolve | complete | 1 / 3 | 1 week ago |
| 42864 | tool.cast_disasm | complete | 1 / 3 | 1 week ago |
| 42865 | analysis.bundle | complete | 1 / 3 | 1 week ago |
| 42866 | capability.graph | complete | 1 / 3 | 1 week ago |
| 42867 | detector.run | complete | 1 / 3 | 1 week ago |
| 42868 | validation.fork | complete | 1 / 3 | 1 week ago |
| 42969 | report.consolidate | complete | 1 / 6 | 1 week ago |
| 42970 | tool.slither | complete | 1 / 2 | 1 week ago |
{
"status": "not_proxy"
}
'forge clean' running (wd: /tmp/slither-8abed42y) 'forge config --json' running 'forge build --build-info --skip ./test/** ./script/** --force' running (wd: /tmp/slither-8abed42y) KeepToken._writeCheckpoint(address,uint256,uint256,uint256,uint256) (contracts/Contract.sol#697-728) uses a dangerous strict equality: - checkpoints[delegatee][id][nCheckpoints - 1].fromTimestamp == block.timestamp (contracts/Contract.sol#710-711) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities KeepToken.getCurrentVotes(address,uint256).result (contracts/Contract.sol#504) is a local variable never initialized Keep.execute(Operation,address,uint256,bytes,Signature[]).txNonce (contracts/Contract.sol#1150) is a local variable never initialized KeepToken.getPriorVotes(address,uint256,uint256).lower (contracts/Contract.sol#543) is a local variable never initialized KeepToken._moveDelegates(address,address,uint256,uint256).dstRepOld (contracts/Contract.sol#676) is a local variable never initialized Keep.execute(Operation,address,uint256,bytes,Signature[]).previous (contracts/Contract.sol#1179) is a local variable never initialized Keep.initialize(Call[],address[],uint256).supply (contracts/Contract.sol#1108) is a local variable never initialized Keep.initialize(Call[],address[],uint256).previous (contracts/Contract.sol#1106) is a local variable never initialized Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables KeepToken._recoverSig(bytes32,address,uint8,bytes32,bytes32).isValid (contracts/Contract.sol#155) is written in both isValid = 1 (contracts/Contract.sol#189) isValid = mload(uint256)(0x00) == f__recoverSig_asm_0 & returndatasize()() == 0x20 & staticcall(uint256,uint256,uint256,uint256,uint256,uint256)(gas()(),signer,m__recoverSig_asm_0,0xa5,0x00,0x20) (contracts/Contract.sol#205-223) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write KeepToken.permit(address,address,bool,uint256,uint8,bytes32,bytes32) (contracts/Contract.sol#442-483) uses timestamp for comparisons Dangerous comparisons: - block.timestamp > deadline (contracts/Contract.sol#453) KeepToken.getPriorVotes(address,uint256,uint256) (contracts/Contract.sol#521-563) uses timestamp for comparisons Dangerous comparisons: - block.timestamp <= timestamp (contracts/Contract.sol#526) KeepToken.delegateBySig(address,address,uint256,uint256,uint8,bytes32,bytes32) (contracts/Contract.sol#584-623) uses timestamp for comparisons Dangerous comparisons: - block.timestamp > deadline (contracts/Contract.sol#595) KeepToken._writeCheckpoint(address,uint256,uint256,uint256,uint256) (contracts/Contract.sol#697-728) uses timestamp for comparisons Dangerous comparisons: - checkpoints[delegatee][id][nCheckpoints - 1].fromTimestamp == block.timestamp (contracts/Contract.sol#710-711) KeepToken._safeCastTo40(uint256) (contracts/Contract.sol#734-738) uses timestamp for comparisons Dangerous comparisons: - x >= (1 << 40) (contracts/Contract.sol#735) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp KeepToken._recoverSig(bytes32,address,uint8,bytes32,bytes32) (contracts/Contract.sol#148-229) uses assembly - INLINE ASM (contracts/Contract.sol#158-226) KeepToken.name() (contracts/Contract.sol#265-278) uses assembly - INLINE ASM (contracts/Contract.sol#268-275) Multicallable.multicall(bytes[]) (contracts/Contract.sol#840-905) uses assembly - INLINE ASM (contracts/Contract.sol#843-904) Keep._execute(Operation,address,uint256,bytes) (contracts/Contract.sol#1243-1287) uses assembly - INLINE ASM (contracts/Contract.sol#1252-1262) - INLINE ASM (contracts/Contract.sol#1268-1277) - INLINE ASM (contracts/Contract.sol#1281-1283) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage KeepToken.safeBatchTransferFrom(address,address,uint256[],uint256[],bytes) (contracts/Contract.sol#371-436) has a high cyclomatic complexity (13). Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity Version constraint ^0.8.4 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html) - FullInlinerNonExpressionSplitArgumentEvaluationOrder - MissingSideEffectsOnSelectorAccess - AbiReencodingHeadOverflowWithStaticArrayCleanup - DirtyBytesArrayToStorage - DataLocationChangeInInternalOverride - NestedCalldataArrayAbiReencodingSizeValidation - SignedImmutables. It is used by: - ^0.8.4 (contracts/Contract.sol#2) Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity Function KeepToken.DOMAIN_SEPARATOR() (contracts/Contract.sol#130-146) is not in mixedCase Variable Keep.CORE_KEY (contracts/Contract.sol#989) is not in mixedCase Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions . analyzed (4 contracts with 100 detectors), 22 result(s) found
{
"solc": {
"status": "ok",
"use_rc": 0,
"version": "0.8.4",
"strategy": "solc-select",
"solc_path": "/var/www/tripwire/.venv/bin/solc",
"install_rc": 0,
"use_stderr": null,
"use_stdout": "Switched global version to 0.8.4",
"install_stderr": null,
"install_stdout": "Version '0.8.4' is already installed, skipping..."
},
"solc_args": "--base-path /tmp/slither-8abed42y --allow-paths /tmp/slither-8abed42y,/tmp/slither-8abed42y/lib,/tmp/slither-8abed42y/src",
"remappings": [],
"source_files": [
"contracts/Contract.sol"
],
"contract_count": 0,
"contract_names": [],
"detector_count": 22,
"workspace_hash": "bbbf87ec4e59a8962a8a19b7c08d892928b50a0d52ad8437b69962916edf72f5",
"duration_seconds": 0.9928727149963379,
"solc_version_requested": "0.8.18"
}
{"no_findings":false,"summary":"Two high-risk access-control issues were identified: uninitialized clone instances can be seized by any caller, and the generic mint role can mint privileged permission IDs and escalate to full control.","findings":[{"severity":"high","category":"upgradeability","title":"Public initializer allows takeover of any uninitialized clone/proxy","exploit_scenario":"The contract is written as a template: the constructor locks only the implementation by setting `quorum = 1`, while `initialize()` is `public` and has no caller restriction. If a clone/proxy is deployed and not initialized atomically in the same transaction, any attacker can call `initialize()` first, choose their own signer set and threshold, run arbitrary setup calls, and permanently seize the wallet.","recommendation":"Initialize clones atomically in the factory/deployer transaction, or add an explicit trusted-initializer/factory check. If this contract is meant to be proxied, use a standard initializer guard pattern that cannot be frontrun on fresh instances.","evidence":"`initialize()` only checks `quorum != 0` before accepting arbitrary `calls`, `signers`, and `threshold`. The constructor sets `quorum = 1` only on the template itself, so fresh proxy/clone storage starts at zero and is claimable by the first external caller.","locations":["contracts/Contract.sol:1067","contracts/Contract.sol:1078","contracts/Contract.sol:1083","contracts/Contract.sol:1089","contracts/Contract.sol:1129"]},{"severity":"high","category":"access_control","title":"Mint permission can self-issue admin keys and seize full control","exploit_scenario":"A user who is granted only the `mint(...)` permission token can call `mint(attacker, CORE_KEY, 1, ...)` to obtain global authorization, or mint privileged selector/SIGN_KEY IDs to themselves and take over governance/execution. In practice, any operational minter role is equivalent to full admin, defeating the contract's function-level permission model.","recommendation":"Reserve all privileged IDs (`CORE_KEY`, `SIGN_KEY`, and function-selector permission IDs) from generic minting. Separate asset IDs from permission IDs, or add explicit checks so only a stronger admin/quorum path can mint permission-bearing tokens.","evidence":"Authorization is balance-based: `_authorized()` accepts either `CORE_KEY` or `uint32(msg.sig)` balances. `mint()` is itself protected only by `_authorized()` and then forwards an arbitrary `id` into `_mint()` with no blocklist for reserved permission IDs.","locations":["contracts/Contract.sol:235","contracts/Contract.sol:989","contracts/Contract.sol:1018","contracts/Contract.sol:1022","contracts/Contract.sol:1298","contracts/Contract.sol:1304"]}]}
OpenAI Codex v0.118.0 (research preview)
--------
workdir: /tmp/codex-27fq1fid
model: gpt-5.4
provider: openai
approval: never
sandbox: read-only
reasoning effort: high
reasoning summaries: none
session id: 019d5697-85bf-7af1-acea-7735c6ecacb0
--------
user
You are a security auditor. Audit this Solidity codebase for exploitable vulnerabilities.
Focus on: reentrancy, access control, upgradeability/proxy issues, signature validation, oracle manipulation,
accounting/invariant breaks, external call patterns, unchecked return values, and dangerous delegatecall.
Scope rules:
- You MUST fully read `AUDIT_BUNDLE.txt` before concluding. It contains the complete contents of all first-party Solidity sources in this workspace.
- Treat `lib/` and `@openzeppelin/` as third-party dependencies; skip them unless there is a concrete misuse angle in first-party code.
- If there are only dependency files and no first-party sources, set no_findings=true and explain in summary.
Output MUST be valid JSON matching the provided schema.
- If you find nothing meaningful: set no_findings=true and findings=[].
- Otherwise: set no_findings=false and include findings with severity/category/title and file/line locations when possible.
Output constraints (IMPORTANT):
- severity MUST be exactly one of: info, low, medium, high, critical (lowercase).
- category MUST be exactly one of: access_control, reentrancy, math, oracle, upgradeability, auth, other.
Keep summary short and concrete.
warning: Codex could not find system bubblewrap on PATH. Please install bubblewrap with your package manager. Codex will use the vendored bubblewrap in the meantime.
exec
/bin/bash -lc pwd in /tmp/codex-27fq1fid
succeeded in 0ms:
/tmp/codex-27fq1fid
exec
/bin/bash -lc 'wc -l AUDIT_BUNDLE.txt' in /tmp/codex-27fq1fid
succeeded in 0ms:
1404 AUDIT_BUNDLE.txt
exec
/bin/bash -lc 'rg --files' in /tmp/codex-27fq1fid
succeeded in 0ms:
codex-output-schema.json
AUDIT_BUNDLE.txt
contracts/Contract.sol
exec
/bin/bash -lc "sed -n '1,250p' AUDIT_BUNDLE.txt" in /tmp/codex-27fq1fid
succeeded in 0ms:
This file concatenates the full contents of first-party Solidity sources for auditing.
Do not treat dependency code under lib/ or @openzeppelin/ as first-party.
================================================================================
FILE: contracts/Contract.sol
================================================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice ERC1155 interface to receive tokens.
/// @author Modified from Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/tokens/ERC1155/ERC1155.sol)
abstract contract ERC1155TokenReceiver {
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes calldata
) public payable virtual returns (bytes4) {
return this.onERC1155Received.selector;
}
function onERC1155BatchReceived(
address,
address,
uint256[] calldata,
uint256[] calldata,
bytes calldata
) public payable virtual returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
}
/// @notice Modern, minimalist, and gas-optimized ERC1155 implementation with Compound-style voting and flexible permissioning scheme.
/// @author Modified from ERC1155V (https://github.com/kalidao/ERC1155V/blob/main/src/ERC1155V.sol)
/// @author Modified from Compound (https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/Comp.sol)
abstract contract KeepToken {
/// -----------------------------------------------------------------------
/// Events
/// -----------------------------------------------------------------------
event DelegateChanged(
address indexed delegator,
address indexed fromDelegate,
address indexed toDelegate,
uint256 id
);
event DelegateVotesChanged(
address indexed delegate,
uint256 indexed id,
uint256 previousBalance,
uint256 newBalance
);
event TransferSingle(
address indexed operator,
address indexed from,
address indexed to,
uint256 id,
uint256 amount
);
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] amounts
);
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
event TransferabilitySet(
address indexed operator,
uint256 indexed id,
bool on
);
event PermissionSet(address indexed operator, uint256 indexed id, bool on);
event UserPermissionSet(
address indexed operator,
address indexed to,
uint256 indexed id,
bool on
);
event URI(string value, uint256 indexed id);
/// -----------------------------------------------------------------------
/// Custom Errors
/// -----------------------------------------------------------------------
error InvalidSig();
error LengthMismatch();
error Unauthorized();
error NonTransferable();
error NotPermitted();
error UnsafeRecipient();
error InvalidRecipient();
error ExpiredSig();
error Undetermined();
error Overflow();
/// -----------------------------------------------------------------------
/// ERC1155 Storage
/// -----------------------------------------------------------------------
mapping(address => mapping(uint256 => uint256)) public balanceOf;
mapping(address => mapping(address => bool)) public isApprovedForAll;
/// -----------------------------------------------------------------------
/// EIP-712 Storage/Logic
/// -----------------------------------------------------------------------
bytes32 internal constant MALLEABILITY_THRESHOLD =
0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0;
mapping(address => uint256) public nonces;
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return
keccak256(
abi.encode(
// `keccak256(
// "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
// )`
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
// `keccak256(bytes("Keep"))`
0x21d66785fec14e4da3d76f3866cf99a28f4da49ec8782c3cab7cf79c1b6fa66b,
// `keccak256("1")`
0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6,
block.chainid,
address(this)
)
);
}
function _recoverSig(
bytes32 hash,
address signer,
uint8 v,
bytes32 r,
bytes32 s
) internal view virtual {
bool isValid;
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits of `signer` in case they are dirty.
for {
signer := shr(96, shl(96, signer))
} signer {
} {
// Load the free memory pointer.
// Simply using the free memory usually costs less if many slots are needed.
let m := mload(0x40)
// Clean the excess bits of `v` in case they are dirty.
v := and(v, 0xff)
// If `s` in lower half order, such that the signature is not malleable.
if iszero(gt(s, MALLEABILITY_THRESHOLD)) {
mstore(m, hash)
mstore(add(m, 0x20), v)
mstore(add(m, 0x40), r)
mstore(add(m, 0x60), s)
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
0x01, // Address of `ecrecover`.
m, // Start of input.
0x80, // Size of input.
m, // Start of output.
0x20 // Size of output.
)
)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
if mul(eq(mload(m), signer), returndatasize()) {
isValid := 1
break
}
}
// `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
let f := shl(224, 0x1626ba7e)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
mstore(add(m, 0x04), hash)
mstore(add(m, 0x24), 0x40) // The offset of the `signature` in the calldata.
mstore(add(m, 0x44), 65) // Store the length of the signature.
mstore(add(m, 0x64), r) // Store `r` of the signature.
mstore(add(m, 0x84), s) // Store `s` of the signature.
mstore8(add(m, 0xa4), v) // Store `v` of the signature.
isValid := and(
and(
// Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
eq(mload(0x00), f),
// Whether the returndata is exactly 0x20 bytes (1 word) long.
eq(returndatasize(), 0x20)
),
// Whether the staticcall does not revert.
// This must be placed at the end of the `and` clause,
// as the arguments are evaluated from right to left.
staticcall(
gas(), // Remaining gas.
signer, // The `signer` address.
m, // Offset of calldata in memory.
0xa5, // Length of calldata in memory.
0x00, // Offset of returndata.
0x20 // Length of returndata to write.
)
)
break
}
}
if (!isValid) revert InvalidSig();
}
/// -----------------------------------------------------------------------
/// ID Storage
/// -----------------------------------------------------------------------
uint256 internal constant SIGN_KEY = uint32(0x6c4b5546); // `execute()`
mapping(uint256 => uint256) public totalSupply;
mapping(uint256 => bool) public transferable;
mapping(uint256 => bool) public permissioned;
mapping(address => mapping(uint256 => bool)) public userPermissioned;
exec
/bin/bash -lc "sed -n '251,500p' AUDIT_BUNDLE.txt" in /tmp/codex-27fq1fid
succeeded in 0ms:
/// -----------------------------------------------------------------------
/// Checkpoint Storage
/// -----------------------------------------------------------------------
mapping(address => mapping(uint256 => address)) internal _delegates;
mapping(address => mapping(uint256 => uint256)) public numCheckpoints;
mapping(address => mapping(uint256 => mapping(uint256 => Checkpoint)))
public checkpoints;
struct Checkpoint {
uint40 fromTimestamp;
uint216 votes;
}
/// -----------------------------------------------------------------------
/// Metadata Logic
/// -----------------------------------------------------------------------
function name() public pure virtual returns (string memory) {
uint256 placeholder;
assembly {
placeholder := sub(
calldatasize(),
add(shr(240, calldataload(sub(calldatasize(), 2))), 2)
)
placeholder := calldataload(add(placeholder, 2))
}
return string(abi.encodePacked(placeholder));
}
/// -----------------------------------------------------------------------
/// ERC165 Logic
/// -----------------------------------------------------------------------
function supportsInterface(
bytes4 interfaceId
) public view virtual returns (bool) {
return
// ERC165 interface ID for ERC165.
interfaceId == this.supportsInterface.selector ||
// ERC165 interface ID for ERC1155.
interfaceId == 0xd9b67a26;
}
/// -----------------------------------------------------------------------
/// ERC1155 Logic
/// -----------------------------------------------------------------------
function balanceOfBatch(
address[] calldata owners,
uint256[] calldata ids
) public view virtual returns (uint256[] memory balances) {
if (owners.length != ids.length) revert LengthMismatch();
balances = new uint256[](owners.length);
for (uint256 i; i < owners.length; ) {
balances[i] = balanceOf[owners[i]][ids[i]];
// Unchecked because the only math done is incrementing
// the array index counter which cannot possibly overflow.
unchecked {
++i;
}
}
}
function setApprovalForAll(
address operator,
bool approved
) public payable virtual {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data
) public payable virtual {
if (msg.sender != from)
if (!isApprovedForAll[from][msg.sender]) revert Unauthorized();
if (!transferable[id]) revert NonTransferable();
if (permissioned[id])
if (!userPermissioned[to][id] || !userPermissioned[from][id])
revert NotPermitted();
// If not transferring SIGN_KEY, update delegation balance.
// Otherwise, prevent transfer to SIGN_KEY holder.
if (id != SIGN_KEY)
_moveDelegates(delegates(from, id), delegates(to, id), id, amount);
else if (balanceOf[to][id] != 0) revert Overflow();
balanceOf[from][id] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to][id] += amount;
}
emit TransferSingle(msg.sender, from, to, id, amount);
if (to.code.length != 0) {
if (
ERC1155TokenReceiver(to).onERC1155Received(
msg.sender,
from,
id,
amount,
data
) != ERC1155TokenReceiver.onERC1155Received.selector
) revert UnsafeRecipient();
} else if (to == address(0)) revert InvalidRecipient();
}
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) public payable virtual {
if (ids.length != amounts.length) revert LengthMismatch();
if (msg.sender != from)
if (!isApprovedForAll[from][msg.sender]) revert Unauthorized();
// Storing these outside the loop saves ~15 gas per iteration.
uint256 id;
uint256 amount;
for (uint256 i; i < ids.length; ) {
id = ids[i];
amount = amounts[i];
if (!transferable[id]) revert NonTransferable();
if (permissioned[id])
if (!userPermissioned[to][id] || !userPermissioned[from][id])
revert NotPermitted();
// If not transferring SIGN_KEY, update delegation balance.
// Otherwise, prevent transfer to SIGN_KEY holder.
if (id != SIGN_KEY)
_moveDelegates(
delegates(from, id),
delegates(to, id),
id,
amount
);
else if (balanceOf[to][id] != 0) revert Overflow();
balanceOf[from][id] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to][id] += amount;
}
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
emit TransferBatch(msg.sender, from, to, ids, amounts);
if (to.code.length != 0) {
if (
ERC1155TokenReceiver(to).onERC1155BatchReceived(
msg.sender,
from,
ids,
amounts,
data
) != ERC1155TokenReceiver.onERC1155BatchReceived.selector
) revert UnsafeRecipient();
} else if (to == address(0)) revert InvalidRecipient();
}
/// -----------------------------------------------------------------------
/// EIP-2612-style Permit Logic
/// -----------------------------------------------------------------------
function permit(
address owner,
address operator,
bool approved,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public payable virtual {
if (owner == address(0)) revert InvalidSig();
if (block.timestamp > deadline) revert ExpiredSig();
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
bytes32 hash = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address operator,bool approved,uint256 nonce,uint256 deadline)"
),
owner,
operator,
approved,
nonces[owner]++,
deadline
)
)
)
);
_recoverSig(hash, owner, v, r, s);
}
isApprovedForAll[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/// -----------------------------------------------------------------------
/// Checkpoint Logic
/// -----------------------------------------------------------------------
function getVotes(
address account,
uint256 id
) public view virtual returns (uint256) {
return getCurrentVotes(account, id);
}
exec
/bin/bash -lc "sed -n '501,750p' AUDIT_BUNDLE.txt" in /tmp/codex-27fq1fid
succeeded in 0ms:
function getCurrentVotes(
address account,
uint256 id
) public view virtual returns (uint256) {
// Unchecked because subtraction only occurs if positive `nCheckpoints`.
unchecked {
uint256 nCheckpoints = numCheckpoints[account][id];
uint256 result;
if (nCheckpoints != 0)
result = checkpoints[account][id][nCheckpoints - 1].votes;
return result;
}
}
function getPastVotes(
address account,
uint256 id,
uint256 timestamp
) public view virtual returns (uint256) {
return getPriorVotes(account, id, timestamp);
}
function getPriorVotes(
address account,
uint256 id,
uint256 timestamp
) public view virtual returns (uint256) {
if (block.timestamp <= timestamp) revert Undetermined();
uint256 nCheckpoints = numCheckpoints[account][id];
if (nCheckpoints == 0) return 0;
// Unchecked because subtraction only occurs if positive `nCheckpoints`.
unchecked {
uint256 prevCheckpoint = nCheckpoints - 1;
if (
checkpoints[account][id][prevCheckpoint].fromTimestamp <=
timestamp
) return checkpoints[account][id][prevCheckpoint].votes;
if (checkpoints[account][id][0].fromTimestamp > timestamp) return 0;
uint256 lower;
uint256 upper = prevCheckpoint;
while (upper > lower) {
uint256 center = upper - (upper - lower) / 2;
Checkpoint memory cp = checkpoints[account][id][center];
if (cp.fromTimestamp == timestamp) {
return cp.votes;
} else if (cp.fromTimestamp < timestamp) {
lower = center;
} else {
upper = center - 1;
}
}
return checkpoints[account][id][lower].votes;
}
}
/// -----------------------------------------------------------------------
/// Delegation Logic
/// -----------------------------------------------------------------------
function delegates(
address account,
uint256 id
) public view virtual returns (address) {
address current = _delegates[account][id];
if (current == address(0)) current = account;
return current;
}
function delegate(address delegatee, uint256 id) public payable virtual {
_delegate(msg.sender, delegatee, id);
}
function delegateBySig(
address delegator,
address delegatee,
uint256 id,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public payable virtual {
if (delegator == address(0)) revert InvalidSig();
if (block.timestamp > deadline) revert ExpiredSig();
// Unchecked because the only math done is incrementing
// the delegator's nonce which cannot realistically overflow.
unchecked {
bytes32 hash = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Delegation(address delegator,address delegatee,uint256 id,uint256 nonce,uint256 deadline)"
),
delegator,
delegatee,
id,
nonces[delegator]++,
deadline
)
)
)
);
_recoverSig(hash, delegator, v, r, s);
}
_delegate(delegator, delegatee, id);
}
function _delegate(
address delegator,
address delegatee,
uint256 id
) internal virtual {
address currentDelegate = delegates(delegator, id);
_delegates[delegator][id] = delegatee;
emit DelegateChanged(delegator, currentDelegate, delegatee, id);
_moveDelegates(
currentDelegate,
delegatee,
id,
balanceOf[delegator][id]
);
}
function _moveDelegates(
address srcRep,
address dstRep,
uint256 id,
uint256 amount
) internal virtual {
if (srcRep != dstRep) {
if (amount != 0) {
if (srcRep != address(0)) {
uint256 srcRepNum = numCheckpoints[srcRep][id];
uint256 srcRepOld;
// Unchecked because subtraction only occurs if positive `srcRepNum`.
unchecked {
srcRepOld = srcRepNum != 0
? checkpoints[srcRep][id][srcRepNum - 1].votes
: 0;
}
_writeCheckpoint(
srcRep,
id,
srcRepNum,
srcRepOld,
srcRepOld - amount
);
}
if (dstRep != address(0)) {
uint256 dstRepNum = numCheckpoints[dstRep][id];
uint256 dstRepOld;
// Unchecked because subtraction only occurs if positive `dstRepNum`.
unchecked {
if (dstRepNum != 0)
dstRepOld = checkpoints[dstRep][id][dstRepNum - 1]
.votes;
}
_writeCheckpoint(
dstRep,
id,
dstRepNum,
dstRepOld,
dstRepOld + amount
);
}
}
}
}
function _writeCheckpoint(
address delegatee,
uint256 id,
uint256 nCheckpoints,
uint256 oldVotes,
uint256 newVotes
) internal virtual {
emit DelegateVotesChanged(delegatee, id, oldVotes, newVotes);
// Unchecked because subtraction only occurs if positive `nCheckpoints`.
unchecked {
if (nCheckpoints != 0) {
if (
checkpoints[delegatee][id][nCheckpoints - 1]
.fromTimestamp == block.timestamp
) {
checkpoints[delegatee][id][nCheckpoints - 1]
.votes = _safeCastTo216(newVotes);
return;
}
}
checkpoints[delegatee][id][nCheckpoints] = Checkpoint(
_safeCastTo40(block.timestamp),
_safeCastTo216(newVotes)
);
// Unchecked because the only math done is incrementing
// checkpoints which cannot realistically overflow.
++numCheckpoints[delegatee][id];
}
}
/// -----------------------------------------------------------------------
/// Safecast Logic
/// -----------------------------------------------------------------------
function _safeCastTo40(uint256 x) internal pure virtual returns (uint40) {
if (x >= (1 << 40)) revert Overflow();
return uint40(x);
}
function _safeCastTo216(uint256 x) internal pure virtual returns (uint216) {
if (x >= (1 << 216)) revert Overflow();
return uint216(x);
}
exec
/bin/bash -lc "sed -n '751,1000p' AUDIT_BUNDLE.txt" in /tmp/codex-27fq1fid
succeeded in 0ms:
/// -----------------------------------------------------------------------
/// Internal Mint/Burn Logic
/// -----------------------------------------------------------------------
function _mint(
address to,
uint256 id,
uint256 amount,
bytes calldata data
) internal virtual {
_safeCastTo216(totalSupply[id] += amount);
// If not minting SIGN_KEY, update delegation balance.
// Otherwise, prevent minting to SIGN_KEY holder.
if (id != SIGN_KEY)
_moveDelegates(address(0), delegates(to, id), id, amount);
else if (balanceOf[to][id] != 0) revert Overflow();
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to][id] += amount;
}
emit TransferSingle(msg.sender, address(0), to, id, amount);
if (to.code.length != 0) {
if (
ERC1155TokenReceiver(to).onERC1155Received(
msg.sender,
address(0),
id,
amount,
data
) != ERC1155TokenReceiver.onERC1155Received.selector
) revert UnsafeRecipient();
} else if (to == address(0)) revert InvalidRecipient();
}
function _burn(address from, uint256 id, uint256 amount) internal virtual {
balanceOf[from][id] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply[id] -= amount;
}
emit TransferSingle(msg.sender, from, address(0), id, amount);
// If not burning SIGN_KEY, update delegation balance.
if (id != SIGN_KEY)
_moveDelegates(delegates(from, id), address(0), id, amount);
}
/// -----------------------------------------------------------------------
/// Internal Permission Logic
/// -----------------------------------------------------------------------
function _setTransferability(uint256 id, bool on) internal virtual {
transferable[id] = on;
emit TransferabilitySet(msg.sender, id, on);
}
function _setPermission(uint256 id, bool on) internal virtual {
permissioned[id] = on;
emit PermissionSet(msg.sender, id, on);
}
function _setUserPermission(
address to,
uint256 id,
bool on
) internal virtual {
userPermissioned[to][id] = on;
emit UserPermissionSet(msg.sender, to, id, on);
}
}
/// @notice Contract that enables a single call to call multiple methods on itself.
/// @author Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/Multicallable.sol)
/// @dev WARNING!
/// Multicallable is NOT SAFE for use in contracts with checks / requires on `msg.value`
/// (e.g. in NFT minting / auction contracts) without a suitable nonce mechanism.
/// It WILL open up your contract to double-spend vulnerabilities / exploits.
/// See: (https://www.paradigm.xyz/2021/08/two-rights-might-make-a-wrong/)
abstract contract Multicallable {
/// @dev Apply `DELEGATECALL` with the current contract to each calldata in `data`,
/// and store the `abi.encode` formatted results of each `DELEGATECALL` into `results`.
/// If any of the `DELEGATECALL`s reverts, the entire transaction is reverted,
/// and the error is bubbled up.
function multicall(
bytes[] calldata data
) public payable virtual returns (bytes[] memory) {
assembly {
mstore(0x00, 0x20)
mstore(0x20, data.length) // Store `data.length` into `results`.
// Early return if no data.
if iszero(data.length) {
return(0x00, 0x40)
}
let results := 0x40
// `shl` 5 is equivalent to multiplying by 0x20.
let end := shl(5, data.length)
// Copy the offsets from calldata into memory.
calldatacopy(0x40, data.offset, end)
// Pointer to the top of the memory (i.e. start of the free memory).
let resultsOffset := end
for {
end := add(results, end)
} 1 {
} {
// The offset of the current bytes in the calldata.
let o := add(data.offset, mload(results))
let memPtr := add(resultsOffset, 0x40)
// Copy the current bytes from calldata to the memory.
calldatacopy(
memPtr,
add(o, 0x20), // The offset of the current bytes' bytes.
calldataload(o) // The length of the current bytes.
)
if iszero(
delegatecall(
gas(),
address(),
memPtr,
calldataload(o),
0x00,
0x00
)
) {
// Bubble up the revert if the delegatecall reverts.
returndatacopy(0x00, 0x00, returndatasize())
revert(0x00, returndatasize())
}
// Append the current `resultsOffset` into `results`.
mstore(results, resultsOffset)
results := add(results, 0x20)
// Append the `returndatasize()`, and the return data.
mstore(memPtr, returndatasize())
returndatacopy(add(memPtr, 0x20), 0x00, returndatasize())
// Advance the `resultsOffset` by `returndatasize() + 0x20`,
// rounded up to the next multiple of 32.
resultsOffset := and(
add(add(resultsOffset, returndatasize()), 0x3f),
0xffffffffffffffe0
)
if iszero(lt(results, end)) {
break
}
}
return(0x00, add(resultsOffset, 0x40))
}
}
}
/// @title Keep
/// @notice Tokenized multisig wallet.
/// @author z0r0z.eth
/// @custom:coauthor @ControlCplusControlV
/// @custom:coauthor boredretard.eth
/// @custom:coauthor vectorized.eth
/// @custom:coauthor horsefacts.eth
/// @custom:coauthor shivanshi.eth
/// @custom:coauthor @0xAlcibiades
/// @custom:coauthor LeXpunK Army
/// @custom:coauthor @0xmichalis
/// @custom:coauthor @iFrostizz
/// @custom:coauthor @m1guelpf
/// @custom:coauthor @asnared
/// @custom:coauthor @0xPhaze
/// @custom:coauthor out.eth
enum Operation {
call,
delegatecall,
create
}
struct Call {
Operation op;
address to;
uint256 value;
bytes data;
}
struct Signature {
address user;
uint8 v;
bytes32 r;
bytes32 s;
}
contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable {
/// -----------------------------------------------------------------------
/// Events
/// -----------------------------------------------------------------------
/// @dev Emitted when Keep executes call.
event Executed(
uint256 indexed nonce,
Operation op,
address to,
uint256 value,
bytes data
);
/// @dev Emitted when Keep relays call.
event Relayed(Call call);
/// @dev Emitted when Keep relays calls.
event Multirelayed(Call[] calls);
/// @dev Emitted when quorum threshold is updated.
event QuorumSet(uint256 threshold);
/// -----------------------------------------------------------------------
/// Custom Errors
/// -----------------------------------------------------------------------
/// @dev Throws if `initialize()` is called more than once.
error AlreadyInit();
/// @dev Throws if quorum exceeds `totalSupply(SIGN_KEY)`.
error QuorumOverSupply();
/// @dev Throws if quorum with `threshold = 0` is set.
error InvalidThreshold();
/// @dev Throws if `execute()` doesn't complete operation.
error ExecuteFailed();
/// -----------------------------------------------------------------------
/// Keep Storage/Logic
/// -----------------------------------------------------------------------
/// @dev Core ID key permission.
uint256 internal immutable CORE_KEY = uint32(type(KeepToken).interfaceId);
/// @dev Default metadata fetcher for `uri()`.
Keep internal immutable uriFetcher;
/// @dev Record of states verifying `execute()`.
exec
/bin/bash -lc "sed -n '1001,1250p' AUDIT_BUNDLE.txt" in /tmp/codex-27fq1fid
succeeded in 0ms:
uint120 public nonce;
/// @dev SIGN_KEY threshold to `execute()`.
uint120 public quorum;
/// @dev Internal ID metadata mapping.
mapping(uint256 => string) internal _uris;
/// @dev ID metadata fetcher.
/// @param id ID to fetch from.
/// @return tokenURI Metadata.
function uri(uint256 id) public view virtual returns (string memory) {
string memory tokenURI = _uris[id];
if (bytes(tokenURI).length > 0) return tokenURI;
else return uriFetcher.uri(id);
}
/// @dev Access control check for ID key balance holders.
/// Initalizes with `address(this)` having implicit permission
/// without writing to storage by checking `totalSupply()` is zero.
/// Otherwise, this permission can be set to additional accounts,
/// including retaining `address(this)`, via `mint()`.
function _authorized() internal view virtual returns (bool) {
if (
(totalSupply[CORE_KEY] == 0 && msg.sender == address(this)) ||
balanceOf[msg.sender][CORE_KEY] != 0 ||
balanceOf[msg.sender][uint32(msg.sig)] != 0
) return true;
else revert Unauthorized();
}
/// -----------------------------------------------------------------------
/// ERC165 Logic
/// -----------------------------------------------------------------------
/// @dev ERC165 interface detection.
/// @param interfaceId ID to check.
/// @return Fetch detection success.
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return
// ERC165 Interface ID for ERC721TokenReceiver.
interfaceId == this.onERC721Received.selector ||
// ERC165 Interface ID for ERC1155TokenReceiver.
interfaceId == type(ERC1155TokenReceiver).interfaceId ||
// ERC165 interface ID for ERC1155MetadataURI.
interfaceId == this.uri.selector ||
// ERC165 Interface IDs for ERC1155.
super.supportsInterface(interfaceId);
}
/// -----------------------------------------------------------------------
/// ERC721 Receiver Logic
/// -----------------------------------------------------------------------
function onERC721Received(
address,
address,
uint256,
bytes calldata
) public payable virtual returns (bytes4) {
return this.onERC721Received.selector;
}
/// -----------------------------------------------------------------------
/// Initialization Logic
/// -----------------------------------------------------------------------
/// @notice Create Keep template.
/// @param _uriFetcher Metadata default.
constructor(Keep _uriFetcher) payable {
uriFetcher = _uriFetcher;
// Deploy as singleton.
quorum = 1;
}
/// @notice Initialize Keep configuration.
/// @param calls Initial Keep operations.
/// @param signers Initial signer set.
/// @param threshold Initial quorum.
function initialize(
Call[] calldata calls,
address[] calldata signers,
uint256 threshold
) public payable virtual {
if (quorum != 0) revert AlreadyInit();
if (threshold == 0) revert InvalidThreshold();
if (threshold > signers.length) revert QuorumOverSupply();
if (calls.length != 0) {
for (uint256 i; i < calls.length; ) {
_execute(
calls[i].op,
calls[i].to,
calls[i].value,
calls[i].data
);
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
}
address previous;
address signer;
uint256 supply;
for (uint256 i; i < signers.length; ) {
signer = signers[i];
// Prevent zero and duplicate signers.
if (previous >= signer) revert InvalidSig();
previous = signer;
emit TransferSingle(tx.origin, address(0), signer, SIGN_KEY, 1);
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++balanceOf[signer][SIGN_KEY];
++supply;
++i;
}
}
totalSupply[SIGN_KEY] = supply;
quorum = uint120(threshold);
}
/// -----------------------------------------------------------------------
/// Execution Logic
/// -----------------------------------------------------------------------
/// @notice Execute operation from Keep with signatures.
/// @param op Enum operation to execute.
/// @param to Address to send operation to.
/// @param value Amount of ETH to send in operation.
/// @param data Payload to send in operation.
/// @param sigs Array of Keep signatures in ascending order by addresses.
function execute(
Operation op,
address to,
uint256 value,
bytes calldata data,
Signature[] calldata sigs
) public payable virtual {
uint120 txNonce;
// Unchecked because the only math done is incrementing
// Keep nonce which cannot realistically overflow.
unchecked {
emit Executed(txNonce = nonce++, op, to, value, data);
}
// Begin signature validation with hashed inputs.
bytes32 hash = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Execute(uint8 op,address to,uint256 value,bytes data,uint120 nonce)"
),
op,
to,
value,
keccak256(data),
txNonce
)
)
)
);
// Start zero in loop to ensure ascending addresses.
address previous;
// Validation is length of quorum threshold.
uint256 threshold = quorum;
// Store outside loop for gas optimization.
Signature calldata sig;
for (uint256 i; i < threshold; ) {
// Load signature items.
sig = sigs[i];
address user = sig.user;
// Check SIGN_KEY balance.
// This also confirms non-zero `user`.
if (balanceOf[user][SIGN_KEY] == 0) revert InvalidSig();
// Check signature recovery.
_recoverSig(hash, user, sig.v, sig.r, sig.s);
// Check against duplicates.
if (previous >= user) revert InvalidSig();
// Memo signature for next iteration until quorum.
previous = user;
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
_execute(op, to, value, data);
}
/// @notice Relay operation from Keep via `execute()` or as ID key holder.
/// @param call Keep operation as struct of `op, to, value, data`.
function relay(Call calldata call) public payable virtual {
_authorized();
_execute(call.op, call.to, call.value, call.data);
emit Relayed(call);
}
/// @notice Relay operations from Keep via `execute()` or as ID key holder.
/// @param calls Keep operations as struct arrays of `op, to, value, data`.
function multirelay(Call[] calldata calls) public payable virtual {
_authorized();
for (uint256 i; i < calls.length; ) {
_execute(calls[i].op, calls[i].to, calls[i].value, calls[i].data);
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
emit Multirelayed(calls);
}
function _execute(
Operation op,
exec
/bin/bash -lc "sed -n '1251,1404p' AUDIT_BUNDLE.txt" in /tmp/codex-27fq1fid
succeeded in 0ms:
address to,
uint256 value,
bytes memory data
) internal virtual {
if (op == Operation.call) {
bool success;
assembly {
success := call(
gas(),
to,
value,
add(data, 0x20),
mload(data),
0,
0
)
}
if (!success) revert ExecuteFailed();
} else if (op == Operation.delegatecall) {
bool success;
assembly {
success := delegatecall(
gas(),
to,
add(data, 0x20),
mload(data),
0,
0
)
}
if (!success) revert ExecuteFailed();
} else {
assembly {
to := create(value, add(data, 0x20), mload(data))
}
if (to == address(0)) revert ExecuteFailed();
}
}
/// -----------------------------------------------------------------------
/// Mint/Burn Logic
/// -----------------------------------------------------------------------
/// @notice ID minter.
/// @param to Recipient of mint.
/// @param id ID to mint.
/// @param amount ID balance to mint.
/// @param data Optional data payload.
function mint(
address to,
uint256 id,
uint256 amount,
bytes calldata data
) public payable virtual {
_authorized();
_mint(to, id, amount, data);
}
/// @notice ID burner.
/// @param from Account to burn from.
/// @param id ID to burn.
/// @param amount Balance to burn.
function burn(
address from,
uint256 id,
uint256 amount
) public payable virtual {
if (msg.sender != from)
if (!isApprovedForAll[from][msg.sender])
if (!_authorized()) revert Unauthorized();
_burn(from, id, amount);
if (id == SIGN_KEY)
if (quorum > totalSupply[SIGN_KEY]) revert QuorumOverSupply();
}
/// -----------------------------------------------------------------------
/// Threshold Setting Logic
/// -----------------------------------------------------------------------
/// @notice Update Keep quorum threshold.
/// @param threshold Signature threshold for `execute()`.
function setQuorum(uint256 threshold) public payable virtual {
_authorized();
if (threshold == 0) revert InvalidThreshold();
if (threshold > totalSupply[SIGN_KEY]) revert QuorumOverSupply();
quorum = uint120(threshold);
emit QuorumSet(threshold);
}
/// -----------------------------------------------------------------------
/// ID Setting Logic
/// -----------------------------------------------------------------------
/// @notice ID transferability setting.
/// @param id ID to set transferability for.
/// @param on Transferability setting.
function setTransferability(uint256 id, bool on) public payable virtual {
_authorized();
_setTransferability(id, on);
}
/// @notice ID transfer permission toggle.
/// @param id ID to set permission for.
/// @param on Permission setting.
/// @dev This sets account-based ID restriction globally.
function setPermission(uint256 id, bool on) public payable virtual {
_authorized();
_setPermission(id, on);
}
/// @notice ID transfer permission setting.
/// @param to Account to set permission for.
/// @param id ID to set permission for.
/// @param on Permission setting.
/// @dev This sets account-based ID restriction specifically.
function setUserPermission(
address to,
uint256 id,
bool on
) public payable virtual {
_authorized();
_setUserPermission(to, id, on);
}
/// @notice ID metadata setting.
/// @param id ID to set metadata for.
/// @param tokenURI Metadata setting.
function setURI(
uint256 id,
string calldata tokenURI
) public payable virtual {
_authorized();
_uris[id] = tokenURI;
emit URI(tokenURI, id);
}
}
exec
/bin/bash -lc "nl -ba contracts/Contract.sol | sed -n '430,560p'" in /tmp/codex-27fq1fid
succeeded in 0ms:
430 ids,
431 amounts,
432 data
433 ) != ERC1155TokenReceiver.onERC1155BatchReceived.selector
434 ) revert UnsafeRecipient();
435 } else if (to == address(0)) revert InvalidRecipient();
436 }
437
438 /// -----------------------------------------------------------------------
439 /// EIP-2612-style Permit Logic
440 /// -----------------------------------------------------------------------
441
442 function permit(
443 address owner,
444 address operator,
445 bool approved,
446 uint256 deadline,
447 uint8 v,
448 bytes32 r,
449 bytes32 s
450 ) public payable virtual {
451 if (owner == address(0)) revert InvalidSig();
452
453 if (block.timestamp > deadline) revert ExpiredSig();
454
455 // Unchecked because the only math done is incrementing
456 // the owner's nonce which cannot realistically overflow.
457 unchecked {
458 bytes32 hash = keccak256(
459 abi.encodePacked(
460 "\x19\x01",
461 DOMAIN_SEPARATOR(),
462 keccak256(
463 abi.encode(
464 keccak256(
465 "Permit(address owner,address operator,bool approved,uint256 nonce,uint256 deadline)"
466 ),
467 owner,
468 operator,
469 approved,
470 nonces[owner]++,
471 deadline
472 )
473 )
474 )
475 );
476
477 _recoverSig(hash, owner, v, r, s);
478 }
479
480 isApprovedForAll[owner][operator] = approved;
481
482 emit ApprovalForAll(owner, operator, approved);
483 }
484
485 /// -----------------------------------------------------------------------
486 /// Checkpoint Logic
487 /// -----------------------------------------------------------------------
488
489 function getVotes(
490 address account,
491 uint256 id
492 ) public view virtual returns (uint256) {
493 return getCurrentVotes(account, id);
494 }
495
496 function getCurrentVotes(
497 address account,
498 uint256 id
499 ) public view virtual returns (uint256) {
500 // Unchecked because subtraction only occurs if positive `nCheckpoints`.
501 unchecked {
502 uint256 nCheckpoints = numCheckpoints[account][id];
503
504 uint256 result;
505
506 if (nCheckpoints != 0)
507 result = checkpoints[account][id][nCheckpoints - 1].votes;
508
509 return result;
510 }
511 }
512
513 function getPastVotes(
514 address account,
515 uint256 id,
516 uint256 timestamp
517 ) public view virtual returns (uint256) {
518 return getPriorVotes(account, id, timestamp);
519 }
520
521 function getPriorVotes(
522 address account,
523 uint256 id,
524 uint256 timestamp
525 ) public view virtual returns (uint256) {
526 if (block.timestamp <= timestamp) revert Undetermined();
527
528 uint256 nCheckpoints = numCheckpoints[account][id];
529
530 if (nCheckpoints == 0) return 0;
531
532 // Unchecked because subtraction only occurs if positive `nCheckpoints`.
533 unchecked {
534 uint256 prevCheckpoint = nCheckpoints - 1;
535
536 if (
537 checkpoints[account][id][prevCheckpoint].fromTimestamp <=
538 timestamp
539 ) return checkpoints[account][id][prevCheckpoint].votes;
540
541 if (checkpoints[account][id][0].fromTimestamp > timestamp) return 0;
542
543 uint256 lower;
544
545 uint256 upper = prevCheckpoint;
546
547 while (upper > lower) {
548 uint256 center = upper - (upper - lower) / 2;
549
550 Checkpoint memory cp = checkpoints[account][id][center];
551
552 if (cp.fromTimestamp == timestamp) {
553 return cp.votes;
554 } else if (cp.fromTimestamp < timestamp) {
555 lower = center;
556 } else {
557 upper = center - 1;
558 }
559 }
560
exec
/bin/bash -lc "nl -ba contracts/Contract.sol | sed -n '560,760p'" in /tmp/codex-27fq1fid
succeeded in 0ms:
560
561 return checkpoints[account][id][lower].votes;
562 }
563 }
564
565 /// -----------------------------------------------------------------------
566 /// Delegation Logic
567 /// -----------------------------------------------------------------------
568
569 function delegates(
570 address account,
571 uint256 id
572 ) public view virtual returns (address) {
573 address current = _delegates[account][id];
574
575 if (current == address(0)) current = account;
576
577 return current;
578 }
579
580 function delegate(address delegatee, uint256 id) public payable virtual {
581 _delegate(msg.sender, delegatee, id);
582 }
583
584 function delegateBySig(
585 address delegator,
586 address delegatee,
587 uint256 id,
588 uint256 deadline,
589 uint8 v,
590 bytes32 r,
591 bytes32 s
592 ) public payable virtual {
593 if (delegator == address(0)) revert InvalidSig();
594
595 if (block.timestamp > deadline) revert ExpiredSig();
596
597 // Unchecked because the only math done is incrementing
598 // the delegator's nonce which cannot realistically overflow.
599 unchecked {
600 bytes32 hash = keccak256(
601 abi.encodePacked(
602 "\x19\x01",
603 DOMAIN_SEPARATOR(),
604 keccak256(
605 abi.encode(
606 keccak256(
607 "Delegation(address delegator,address delegatee,uint256 id,uint256 nonce,uint256 deadline)"
608 ),
609 delegator,
610 delegatee,
611 id,
612 nonces[delegator]++,
613 deadline
614 )
615 )
616 )
617 );
618
619 _recoverSig(hash, delegator, v, r, s);
620 }
621
622 _delegate(delegator, delegatee, id);
623 }
624
625 function _delegate(
626 address delegator,
627 address delegatee,
628 uint256 id
629 ) internal virtual {
630 address currentDelegate = delegates(delegator, id);
631
632 _delegates[delegator][id] = delegatee;
633
634 emit DelegateChanged(delegator, currentDelegate, delegatee, id);
635
636 _moveDelegates(
637 currentDelegate,
638 delegatee,
639 id,
640 balanceOf[delegator][id]
641 );
642 }
643
644 function _moveDelegates(
645 address srcRep,
646 address dstRep,
647 uint256 id,
648 uint256 amount
649 ) internal virtual {
650 if (srcRep != dstRep) {
651 if (amount != 0) {
652 if (srcRep != address(0)) {
653 uint256 srcRepNum = numCheckpoints[srcRep][id];
654
655 uint256 srcRepOld;
656
657 // Unchecked because subtraction only occurs if positive `srcRepNum`.
658 unchecked {
659 srcRepOld = srcRepNum != 0
660 ? checkpoints[srcRep][id][srcRepNum - 1].votes
661 : 0;
662 }
663
664 _writeCheckpoint(
665 srcRep,
666 id,
667 srcRepNum,
668 srcRepOld,
669 srcRepOld - amount
670 );
671 }
672
673 if (dstRep != address(0)) {
674 uint256 dstRepNum = numCheckpoints[dstRep][id];
675
676 uint256 dstRepOld;
677
678 // Unchecked because subtraction only occurs if positive `dstRepNum`.
679 unchecked {
680 if (dstRepNum != 0)
681 dstRepOld = checkpoints[dstRep][id][dstRepNum - 1]
682 .votes;
683 }
684
685 _writeCheckpoint(
686 dstRep,
687 id,
688 dstRepNum,
689 dstRepOld,
690 dstRepOld + amount
691 );
692 }
693 }
694 }
695 }
696
697 function _writeCheckpoint(
698 address delegatee,
699 uint256 id,
700 uint256 nCheckpoints,
701 uint256 oldVotes,
702 uint256 newVotes
703 ) internal virtual {
704 emit DelegateVotesChanged(delegatee, id, oldVotes, newVotes);
705
706 // Unchecked because subtraction only occurs if positive `nCheckpoints`.
707 unchecked {
708 if (nCheckpoints != 0) {
709 if (
710 checkpoints[delegatee][id][nCheckpoints - 1]
711 .fromTimestamp == block.timestamp
712 ) {
713 checkpoints[delegatee][id][nCheckpoints - 1]
714 .votes = _safeCastTo216(newVotes);
715 return;
716 }
717 }
718
719 checkpoints[delegatee][id][nCheckpoints] = Checkpoint(
720 _safeCastTo40(block.timestamp),
721 _safeCastTo216(newVotes)
722 );
723
724 // Unchecked because the only math done is incrementing
725 // checkpoints which cannot realistically overflow.
726 ++numCheckpoints[delegatee][id];
727 }
728 }
729
730 /// -----------------------------------------------------------------------
731 /// Safecast Logic
732 /// -----------------------------------------------------------------------
733
734 function _safeCastTo40(uint256 x) internal pure virtual returns (uint40) {
735 if (x >= (1 << 40)) revert Overflow();
736
737 return uint40(x);
738 }
739
740 function _safeCastTo216(uint256 x) internal pure virtual returns (uint216) {
741 if (x >= (1 << 216)) revert Overflow();
742
743 return uint216(x);
744 }
745
746 /// -----------------------------------------------------------------------
747 /// Internal Mint/Burn Logic
748 /// -----------------------------------------------------------------------
749
750 function _mint(
751 address to,
752 uint256 id,
753 uint256 amount,
754 bytes calldata data
755 ) internal virtual {
756 _safeCastTo216(totalSupply[id] += amount);
757
758 // If not minting SIGN_KEY, update delegation balance.
759 // Otherwise, prevent minting to SIGN_KEY holder.
760 if (id != SIGN_KEY)
exec
/bin/bash -lc "nl -ba contracts/Contract.sol | sed -n '760,920p'" in /tmp/codex-27fq1fid
succeeded in 0ms:
760 if (id != SIGN_KEY)
761 _moveDelegates(address(0), delegates(to, id), id, amount);
762 else if (balanceOf[to][id] != 0) revert Overflow();
763
764 // Cannot overflow because the sum of all user
765 // balances can't exceed the max uint256 value.
766 unchecked {
767 balanceOf[to][id] += amount;
768 }
769
770 emit TransferSingle(msg.sender, address(0), to, id, amount);
771
772 if (to.code.length != 0) {
773 if (
774 ERC1155TokenReceiver(to).onERC1155Received(
775 msg.sender,
776 address(0),
777 id,
778 amount,
779 data
780 ) != ERC1155TokenReceiver.onERC1155Received.selector
781 ) revert UnsafeRecipient();
782 } else if (to == address(0)) revert InvalidRecipient();
783 }
784
785 function _burn(address from, uint256 id, uint256 amount) internal virtual {
786 balanceOf[from][id] -= amount;
787
788 // Cannot underflow because a user's balance
789 // will never be larger than the total supply.
790 unchecked {
791 totalSupply[id] -= amount;
792 }
793
794 emit TransferSingle(msg.sender, from, address(0), id, amount);
795
796 // If not burning SIGN_KEY, update delegation balance.
797 if (id != SIGN_KEY)
798 _moveDelegates(delegates(from, id), address(0), id, amount);
799 }
800
801 /// -----------------------------------------------------------------------
802 /// Internal Permission Logic
803 /// -----------------------------------------------------------------------
804
805 function _setTransferability(uint256 id, bool on) internal virtual {
806 transferable[id] = on;
807
808 emit TransferabilitySet(msg.sender, id, on);
809 }
810
811 function _setPermission(uint256 id, bool on) internal virtual {
812 permissioned[id] = on;
813
814 emit PermissionSet(msg.sender, id, on);
815 }
816
817 function _setUserPermission(
818 address to,
819 uint256 id,
820 bool on
821 ) internal virtual {
822 userPermissioned[to][id] = on;
823
824 emit UserPermissionSet(msg.sender, to, id, on);
825 }
826 }
827
828 /// @notice Contract that enables a single call to call multiple methods on itself.
829 /// @author Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/Multicallable.sol)
830 /// @dev WARNING!
831 /// Multicallable is NOT SAFE for use in contracts with checks / requires on `msg.value`
832 /// (e.g. in NFT minting / auction contracts) without a suitable nonce mechanism.
833 /// It WILL open up your contract to double-spend vulnerabilities / exploits.
834 /// See: (https://www.paradigm.xyz/2021/08/two-rights-might-make-a-wrong/)
835 abstract contract Multicallable {
836 /// @dev Apply `DELEGATECALL` with the current contract to each calldata in `data`,
837 /// and store the `abi.encode` formatted results of each `DELEGATECALL` into `results`.
838 /// If any of the `DELEGATECALL`s reverts, the entire transaction is reverted,
839 /// and the error is bubbled up.
840 function multicall(
841 bytes[] calldata data
842 ) public payable virtual returns (bytes[] memory) {
843 assembly {
844 mstore(0x00, 0x20)
845 mstore(0x20, data.length) // Store `data.length` into `results`.
846 // Early return if no data.
847 if iszero(data.length) {
848 return(0x00, 0x40)
849 }
850
851 let results := 0x40
852 // `shl` 5 is equivalent to multiplying by 0x20.
853 let end := shl(5, data.length)
854 // Copy the offsets from calldata into memory.
855 calldatacopy(0x40, data.offset, end)
856 // Pointer to the top of the memory (i.e. start of the free memory).
857 let resultsOffset := end
858
859 for {
860 end := add(results, end)
861 } 1 {
862
863 } {
864 // The offset of the current bytes in the calldata.
865 let o := add(data.offset, mload(results))
866 let memPtr := add(resultsOffset, 0x40)
867 // Copy the current bytes from calldata to the memory.
868 calldatacopy(
869 memPtr,
870 add(o, 0x20), // The offset of the current bytes' bytes.
871 calldataload(o) // The length of the current bytes.
872 )
873 if iszero(
874 delegatecall(
875 gas(),
876 address(),
877 memPtr,
878 calldataload(o),
879 0x00,
880 0x00
881 )
882 ) {
883 // Bubble up the revert if the delegatecall reverts.
884 returndatacopy(0x00, 0x00, returndatasize())
885 revert(0x00, returndatasize())
886 }
887 // Append the current `resultsOffset` into `results`.
888 mstore(results, resultsOffset)
889 results := add(results, 0x20)
890 // Append the `returndatasize()`, and the return data.
891 mstore(memPtr, returndatasize())
892 returndatacopy(add(memPtr, 0x20), 0x00, returndatasize())
893 // Advance the `resultsOffset` by `returndatasize() + 0x20`,
894 // rounded up to the next multiple of 32.
895 resultsOffset := and(
896 add(add(resultsOffset, returndatasize()), 0x3f),
897 0xffffffffffffffe0
898 )
899 if iszero(lt(results, end)) {
900 break
901 }
902 }
903 return(0x00, add(resultsOffset, 0x40))
904 }
905 }
906 }
907
908 /// @title Keep
909 /// @notice Tokenized multisig wallet.
910 /// @author z0r0z.eth
911 /// @custom:coauthor @ControlCplusControlV
912 /// @custom:coauthor boredretard.eth
913 /// @custom:coauthor vectorized.eth
914 /// @custom:coauthor horsefacts.eth
915 /// @custom:coauthor shivanshi.eth
916 /// @custom:coauthor @0xAlcibiades
917 /// @custom:coauthor LeXpunK Army
918 /// @custom:coauthor @0xmichalis
919 /// @custom:coauthor @iFrostizz
920 /// @custom:coauthor @m1guelpf
exec
/bin/bash -lc "nl -ba contracts/Contract.sol | sed -n '920,1095p'" in /tmp/codex-27fq1fid
succeeded in 0ms:
920 /// @custom:coauthor @m1guelpf
921 /// @custom:coauthor @asnared
922 /// @custom:coauthor @0xPhaze
923 /// @custom:coauthor out.eth
924
925 enum Operation {
926 call,
927 delegatecall,
928 create
929 }
930
931 struct Call {
932 Operation op;
933 address to;
934 uint256 value;
935 bytes data;
936 }
937
938 struct Signature {
939 address user;
940 uint8 v;
941 bytes32 r;
942 bytes32 s;
943 }
944
945 contract Keep is ERC1155TokenReceiver, KeepToken, Multicallable {
946 /// -----------------------------------------------------------------------
947 /// Events
948 /// -----------------------------------------------------------------------
949
950 /// @dev Emitted when Keep executes call.
951 event Executed(
952 uint256 indexed nonce,
953 Operation op,
954 address to,
955 uint256 value,
956 bytes data
957 );
958
959 /// @dev Emitted when Keep relays call.
960 event Relayed(Call call);
961
962 /// @dev Emitted when Keep relays calls.
963 event Multirelayed(Call[] calls);
964
965 /// @dev Emitted when quorum threshold is updated.
966 event QuorumSet(uint256 threshold);
967
968 /// -----------------------------------------------------------------------
969 /// Custom Errors
970 /// -----------------------------------------------------------------------
971
972 /// @dev Throws if `initialize()` is called more than once.
973 error AlreadyInit();
974
975 /// @dev Throws if quorum exceeds `totalSupply(SIGN_KEY)`.
976 error QuorumOverSupply();
977
978 /// @dev Throws if quorum with `threshold = 0` is set.
979 error InvalidThreshold();
980
981 /// @dev Throws if `execute()` doesn't complete operation.
982 error ExecuteFailed();
983
984 /// -----------------------------------------------------------------------
985 /// Keep Storage/Logic
986 /// -----------------------------------------------------------------------
987
988 /// @dev Core ID key permission.
989 uint256 internal immutable CORE_KEY = uint32(type(KeepToken).interfaceId);
990
991 /// @dev Default metadata fetcher for `uri()`.
992 Keep internal immutable uriFetcher;
993
994 /// @dev Record of states verifying `execute()`.
995 uint120 public nonce;
996
997 /// @dev SIGN_KEY threshold to `execute()`.
998 uint120 public quorum;
999
1000 /// @dev Internal ID metadata mapping.
1001 mapping(uint256 => string) internal _uris;
1002
1003 /// @dev ID metadata fetcher.
1004 /// @param id ID to fetch from.
1005 /// @return tokenURI Metadata.
1006 function uri(uint256 id) public view virtual returns (string memory) {
1007 string memory tokenURI = _uris[id];
1008
1009 if (bytes(tokenURI).length > 0) return tokenURI;
1010 else return uriFetcher.uri(id);
1011 }
1012
1013 /// @dev Access control check for ID key balance holders.
1014 /// Initalizes with `address(this)` having implicit permission
1015 /// without writing to storage by checking `totalSupply()` is zero.
1016 /// Otherwise, this permission can be set to additional accounts,
1017 /// including retaining `address(this)`, via `mint()`.
1018 function _authorized() internal view virtual returns (bool) {
1019 if (
1020 (totalSupply[CORE_KEY] == 0 && msg.sender == address(this)) ||
1021 balanceOf[msg.sender][CORE_KEY] != 0 ||
1022 balanceOf[msg.sender][uint32(msg.sig)] != 0
1023 ) return true;
1024 else revert Unauthorized();
1025 }
1026
1027 /// -----------------------------------------------------------------------
1028 /// ERC165 Logic
1029 /// -----------------------------------------------------------------------
1030
1031 /// @dev ERC165 interface detection.
1032 /// @param interfaceId ID to check.
1033 /// @return Fetch detection success.
1034 function supportsInterface(
1035 bytes4 interfaceId
1036 ) public view virtual override returns (bool) {
1037 return
1038 // ERC165 Interface ID for ERC721TokenReceiver.
1039 interfaceId == this.onERC721Received.selector ||
1040 // ERC165 Interface ID for ERC1155TokenReceiver.
1041 interfaceId == type(ERC1155TokenReceiver).interfaceId ||
1042 // ERC165 interface ID for ERC1155MetadataURI.
1043 interfaceId == this.uri.selector ||
1044 // ERC165 Interface IDs for ERC1155.
1045 super.supportsInterface(interfaceId);
1046 }
1047
1048 /// -----------------------------------------------------------------------
1049 /// ERC721 Receiver Logic
1050 /// -----------------------------------------------------------------------
1051
1052 function onERC721Received(
1053 address,
1054 address,
1055 uint256,
1056 bytes calldata
1057 ) public payable virtual returns (bytes4) {
1058 return this.onERC721Received.selector;
1059 }
1060
1061 /// -----------------------------------------------------------------------
1062 /// Initialization Logic
1063 /// -----------------------------------------------------------------------
1064
1065 /// @notice Create Keep template.
1066 /// @param _uriFetcher Metadata default.
1067 constructor(Keep _uriFetcher) payable {
1068 uriFetcher = _uriFetcher;
1069
1070 // Deploy as singleton.
1071 quorum = 1;
1072 }
1073
1074 /// @notice Initialize Keep configuration.
1075 /// @param calls Initial Keep operations.
1076 /// @param signers Initial signer set.
1077 /// @param threshold Initial quorum.
1078 function initialize(
1079 Call[] calldata calls,
1080 address[] calldata signers,
1081 uint256 threshold
1082 ) public payable virtual {
1083 if (quorum != 0) revert AlreadyInit();
1084
1085 if (threshold == 0) revert InvalidThreshold();
1086
1087 if (threshold > signers.length) revert QuorumOverSupply();
1088
1089 if (calls.length != 0) {
1090 for (uint256 i; i < calls.length; ) {
1091 _execute(
1092 calls[i].op,
1093 calls[i].to,
1094 calls[i].value,
1095 calls[i].data
exec
/bin/bash -lc "nl -ba contracts/Contract.sol | sed -n '1095,1285p'" in /tmp/codex-27fq1fid
succeeded in 0ms:
1095 calls[i].data
1096 );
1097
1098 // An array can't have a total length
1099 // larger than the max uint256 value.
1100 unchecked {
1101 ++i;
1102 }
1103 }
1104 }
1105
1106 address previous;
1107 address signer;
1108 uint256 supply;
1109
1110 for (uint256 i; i < signers.length; ) {
1111 signer = signers[i];
1112
1113 // Prevent zero and duplicate signers.
1114 if (previous >= signer) revert InvalidSig();
1115
1116 previous = signer;
1117
1118 emit TransferSingle(tx.origin, address(0), signer, SIGN_KEY, 1);
1119
1120 // An array can't have a total length
1121 // larger than the max uint256 value.
1122 unchecked {
1123 ++balanceOf[signer][SIGN_KEY];
1124 ++supply;
1125 ++i;
1126 }
1127 }
1128
1129 totalSupply[SIGN_KEY] = supply;
1130 quorum = uint120(threshold);
1131 }
1132
1133 /// -----------------------------------------------------------------------
1134 /// Execution Logic
1135 /// -----------------------------------------------------------------------
1136
1137 /// @notice Execute operation from Keep with signatures.
1138 /// @param op Enum operation to execute.
1139 /// @param to Address to send operation to.
1140 /// @param value Amount of ETH to send in operation.
1141 /// @param data Payload to send in operation.
1142 /// @param sigs Array of Keep signatures in ascending order by addresses.
1143 function execute(
1144 Operation op,
1145 address to,
1146 uint256 value,
1147 bytes calldata data,
1148 Signature[] calldata sigs
1149 ) public payable virtual {
1150 uint120 txNonce;
1151
1152 // Unchecked because the only math done is incrementing
1153 // Keep nonce which cannot realistically overflow.
1154 unchecked {
1155 emit Executed(txNonce = nonce++, op, to, value, data);
1156 }
1157
1158 // Begin signature validation with hashed inputs.
1159 bytes32 hash = keccak256(
1160 abi.encodePacked(
1161 "\x19\x01",
1162 DOMAIN_SEPARATOR(),
1163 keccak256(
1164 abi.encode(
1165 keccak256(
1166 "Execute(uint8 op,address to,uint256 value,bytes data,uint120 nonce)"
1167 ),
1168 op,
1169 to,
1170 value,
1171 keccak256(data),
1172 txNonce
1173 )
1174 )
1175 )
1176 );
1177
1178 // Start zero in loop to ensure ascending addresses.
1179 address previous;
1180
1181 // Validation is length of quorum threshold.
1182 uint256 threshold = quorum;
1183
1184 // Store outside loop for gas optimization.
1185 Signature calldata sig;
1186
1187 for (uint256 i; i < threshold; ) {
1188 // Load signature items.
1189 sig = sigs[i];
1190 address user = sig.user;
1191
1192 // Check SIGN_KEY balance.
1193 // This also confirms non-zero `user`.
1194 if (balanceOf[user][SIGN_KEY] == 0) revert InvalidSig();
1195
1196 // Check signature recovery.
1197 _recoverSig(hash, user, sig.v, sig.r, sig.s);
1198
1199 // Check against duplicates.
1200 if (previous >= user) revert InvalidSig();
1201
1202 // Memo signature for next iteration until quorum.
1203 previous = user;
1204
1205 // An array can't have a total length
1206 // larger than the max uint256 value.
1207 unchecked {
1208 ++i;
1209 }
1210 }
1211
1212 _execute(op, to, value, data);
1213 }
1214
1215 /// @notice Relay operation from Keep via `execute()` or as ID key holder.
1216 /// @param call Keep operation as struct of `op, to, value, data`.
1217 function relay(Call calldata call) public payable virtual {
1218 _authorized();
1219
1220 _execute(call.op, call.to, call.value, call.data);
1221
1222 emit Relayed(call);
1223 }
1224
1225 /// @notice Relay operations from Keep via `execute()` or as ID key holder.
1226 /// @param calls Keep operations as struct arrays of `op, to, value, data`.
1227 function multirelay(Call[] calldata calls) public payable virtual {
1228 _authorized();
1229
1230 for (uint256 i; i < calls.length; ) {
1231 _execute(calls[i].op, calls[i].to, calls[i].value, calls[i].data);
1232
1233 // An array can't have a total length
1234 // larger than the max uint256 value.
1235 unchecked {
1236 ++i;
1237 }
1238 }
1239
1240 emit Multirelayed(calls);
1241 }
1242
1243 function _execute(
1244 Operation op,
1245 address to,
1246 uint256 value,
1247 bytes memory data
1248 ) internal virtual {
1249 if (op == Operation.call) {
1250 bool success;
1251
1252 assembly {
1253 success := call(
1254 gas(),
1255 to,
1256 value,
1257 add(data, 0x20),
1258 mload(data),
1259 0,
1260 0
1261 )
1262 }
1263
1264 if (!success) revert ExecuteFailed();
1265 } else if (op == Operation.delegatecall) {
1266 bool success;
1267
1268 assembly {
1269 success := delegatecall(
1270 gas(),
1271 to,
1272 add(data, 0x20),
1273 mload(data),
1274 0,
1275 0
1276 )
1277 }
1278
1279 if (!success) revert ExecuteFailed();
1280 } else {
1281 assembly {
1282 to := create(value, add(data, 0x20), mload(data))
1283 }
1284
1285 if (to == address(0)) revert ExecuteFailed();
exec
/bin/bash -lc "nl -ba contracts/Contract.sol | sed -n '1285,1375p'" in /tmp/codex-27fq1fid
succeeded in 0ms:
1285 if (to == address(0)) revert ExecuteFailed();
1286 }
1287 }
1288
1289 /// -----------------------------------------------------------------------
1290 /// Mint/Burn Logic
1291 /// -----------------------------------------------------------------------
1292
1293 /// @notice ID minter.
1294 /// @param to Recipient of mint.
1295 /// @param id ID to mint.
1296 /// @param amount ID balance to mint.
1297 /// @param data Optional data payload.
1298 function mint(
1299 address to,
1300 uint256 id,
1301 uint256 amount,
1302 bytes calldata data
1303 ) public payable virtual {
1304 _authorized();
1305
1306 _mint(to, id, amount, data);
1307 }
1308
1309 /// @notice ID burner.
1310 /// @param from Account to burn from.
1311 /// @param id ID to burn.
1312 /// @param amount Balance to burn.
1313 function burn(
1314 address from,
1315 uint256 id,
1316 uint256 amount
1317 ) public payable virtual {
1318 if (msg.sender != from)
1319 if (!isApprovedForAll[from][msg.sender])
1320 if (!_authorized()) revert Unauthorized();
1321
1322 _burn(from, id, amount);
1323
1324 if (id == SIGN_KEY)
1325 if (quorum > totalSupply[SIGN_KEY]) revert QuorumOverSupply();
1326 }
1327
1328 /// -----------------------------------------------------------------------
1329 /// Threshold Setting Logic
1330 /// -----------------------------------------------------------------------
1331
1332 /// @notice Update Keep quorum threshold.
1333 /// @param threshold Signature threshold for `execute()`.
1334 function setQuorum(uint256 threshold) public payable virtual {
1335 _authorized();
1336
1337 if (threshold == 0) revert InvalidThreshold();
1338
1339 if (threshold > totalSupply[SIGN_KEY]) revert QuorumOverSupply();
1340
1341 quorum = uint120(threshold);
1342
1343 emit QuorumSet(threshold);
1344 }
1345
1346 /// -----------------------------------------------------------------------
1347 /// ID Setting Logic
1348 /// -----------------------------------------------------------------------
1349
1350 /// @notice ID transferability setting.
1351 /// @param id ID to set transferability for.
1352 /// @param on Transferability setting.
1353 function setTransferability(uint256 id, bool on) public payable virtual {
1354 _authorized();
1355
1356 _setTransferability(id, on);
1357 }
1358
1359 /// @notice ID transfer permission toggle.
1360 /// @param id ID to set permission for.
1361 /// @param on Permission setting.
1362 /// @dev This sets account-based ID restriction globally.
1363 function setPermission(uint256 id, bool on) public payable virtual {
1364 _authorized();
1365
1366 _setPermission(id, on);
1367 }
1368
1369 /// @notice ID transfer permission setting.
1370 /// @param to Account to set permission for.
1371 /// @param id ID to set permission for.
1372 /// @param on Permission setting.
1373 /// @dev This sets account-based ID restriction specifically.
1374 function setUserPermission(
1375 address to,
exec
/bin/bash -lc "nl -ba contracts/Contract.sol | sed -n '1375,1415p'" in /tmp/codex-27fq1fid
succeeded in 0ms:
1375 address to,
1376 uint256 id,
1377 bool on
1378 ) public payable virtual {
1379 _authorized();
1380
1381 _setUserPermission(to, id, on);
1382 }
1383
1384 /// @notice ID metadata setting.
1385 /// @param id ID to set metadata for.
1386 /// @param tokenURI Metadata setting.
1387 function setURI(
1388 uint256 id,
1389 string calldata tokenURI
1390 ) public payable virtual {
1391 _authorized();
1392
1393 _uris[id] = tokenURI;
1394
1395 emit URI(tokenURI, id);
1396 }
1397 }
exec
/bin/bash -lc "nl -ba contracts/Contract.sol | sed -n '180,240p'" in /tmp/codex-27fq1fid
succeeded in 0ms:
180 0x01, // Address of `ecrecover`.
181 m, // Start of input.
182 0x80, // Size of input.
183 m, // Start of output.
184 0x20 // Size of output.
185 )
186 )
187 // `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
188 if mul(eq(mload(m), signer), returndatasize()) {
189 isValid := 1
190 break
191 }
192 }
193
194 // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
195 let f := shl(224, 0x1626ba7e)
196 // Write the abi-encoded calldata into memory, beginning with the function selector.
197 mstore(m, f) // `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`.
198 mstore(add(m, 0x04), hash)
199 mstore(add(m, 0x24), 0x40) // The offset of the `signature` in the calldata.
200 mstore(add(m, 0x44), 65) // Store the length of the signature.
201 mstore(add(m, 0x64), r) // Store `r` of the signature.
202 mstore(add(m, 0x84), s) // Store `s` of the signature.
203 mstore8(add(m, 0xa4), v) // Store `v` of the signature.
204
205 isValid := and(
206 and(
207 // Whether the returndata is the magic value `0x1626ba7e` (left-aligned).
208 eq(mload(0x00), f),
209 // Whether the returndata is exactly 0x20 bytes (1 word) long.
210 eq(returndatasize(), 0x20)
211 ),
212 // Whether the staticcall does not revert.
213 // This must be placed at the end of the `and` clause,
214 // as the arguments are evaluated from right to left.
215 staticcall(
216 gas(), // Remaining gas.
217 signer, // The `signer` address.
218 m, // Offset of calldata in memory.
219 0xa5, // Length of calldata in memory.
220 0x00, // Offset of returndata.
221 0x20 // Length of returndata to write.
222 )
223 )
224 break
225 }
226 }
227
228 if (!isValid) revert InvalidSig();
229 }
230
231 /// -----------------------------------------------------------------------
232 /// ID Storage
233 /// -----------------------------------------------------------------------
234
235 uint256 internal constant SIGN_KEY = uint32(0x6c4b5546); // `execute()`
236
237 mapping(uint256 => uint256) public totalSupply;
238
239 mapping(uint256 => bool) public transferable;
240
codex
{"no_findings":false,"summary":"Two high-risk access-control issues were identified: uninitialized clone instances can be seized by any caller, and the generic mint role can mint privileged permission IDs and escalate to full control.","findings":[{"severity":"high","category":"upgradeability","title":"Public initializer allows takeover of any uninitialized clone/proxy","exploit_scenario":"The contract is written as a template: the constructor locks only the implementation by setting `quorum = 1`, while `initialize()` is `public` and has no caller restriction. If a clone/proxy is deployed and not initialized atomically in the same transaction, any attacker can call `initialize()` first, choose their own signer set and threshold, run arbitrary setup calls, and permanently seize the wallet.","recommendation":"Initialize clones atomically in the factory/deployer transaction, or add an explicit trusted-initializer/factory check. If this contract is meant to be proxied, use a standard initializer guard pattern that cannot be frontrun on fresh instances.","evidence":"`initialize()` only checks `quorum != 0` before accepting arbitrary `calls`, `signers`, and `threshold`. The constructor sets `quorum = 1` only on the template itself, so fresh proxy/clone storage starts at zero and is claimable by the first external caller.","locations":["contracts/Contract.sol:1067","contracts/Contract.sol:1078","contracts/Contract.sol:1083","contracts/Contract.sol:1089","contracts/Contract.sol:1129"]},{"severity":"high","category":"access_control","title":"Mint permission can self-issue admin keys and seize full control","exploit_scenario":"A user who is granted only the `mint(...)` permission token can call `mint(attacker, CORE_KEY, 1, ...)` to obtain global authorization, or mint privileged selector/SIGN_KEY IDs to themselves and take over governance/execution. In practice, any operational minter role is equivalent to full admin, defeating the contract's function-level permission model.","recommendation":"Reserve all privileged IDs (`CORE_KEY`, `SIGN_KEY`, and function-selector permission IDs) from generic minting. Separate asset IDs from permission IDs, or add explicit checks so only a stronger admin/quorum path can mint permission-bearing tokens.","evidence":"Authorization is balance-based: `_authorized()` accepts either `CORE_KEY` or `uint32(msg.sig)` balances. `mint()` is itself protected only by `_authorized()` and then forwards an arbitrary `id` into `_mint()` with no blocklist for reserved permission IDs.","locations":["contracts/Contract.sol:235","contracts/Contract.sol:989","contracts/Contract.sol:1018","contracts/Contract.sol:1022","contracts/Contract.sol:1298","contracts/Contract.sol:1304"]}]}
tokens used
57,019
{
"codex": {
"ran_at": "2026-04-04T03:47:58.708608+00:00",
"returncode": 0
},
"project": {
"mode": "source",
"file_count": 1,
"import_count": 0,
"first_party_contracts_count": 1
},
"summary": "Two high-risk access-control issues were identified: uninitialized clone instances can be seized by any caller, and the generic mint role can mint privileged permission IDs and escalate to full control.",
"raw_final": {
"summary": "Two high-risk access-control issues were identified: uninitialized clone instances can be seized by any caller, and the generic mint role can mint privileged permission IDs and escalate to full control.",
"findings": [
{
"title": "Public initializer allows takeover of any uninitialized clone/proxy",
"category": "upgradeability",
"evidence": "`initialize()` only checks `quorum != 0` before accepting arbitrary `calls`, `signers`, and `threshold`. The constructor sets `quorum = 1` only on the template itself, so fresh proxy/clone storage starts at zero and is claimable by the first external caller.",
"severity": "high",
"locations": [
"contracts/Contract.sol:1067",
"contracts/Contract.sol:1078",
"contracts/Contract.sol:1083",
"contracts/Contract.sol:1089",
"contracts/Contract.sol:1129"
],
"recommendation": "Initialize clones atomically in the factory/deployer transaction, or add an explicit trusted-initializer/factory check. If this contract is meant to be proxied, use a standard initializer guard pattern that cannot be frontrun on fresh instances.",
"exploit_scenario": "The contract is written as a template: the constructor locks only the implementation by setting `quorum = 1`, while `initialize()` is `public` and has no caller restriction. If a clone/proxy is deployed and not initialized atomically in the same transaction, any attacker can call `initialize()` first, choose their own signer set and threshold, run arbitrary setup calls, and permanently seize the wallet."
},
{
"title": "Mint permission can self-issue admin keys and seize full control",
"category": "access_control",
"evidence": "Authorization is balance-based: `_authorized()` accepts either `CORE_KEY` or `uint32(msg.sig)` balances. `mint()` is itself protected only by `_authorized()` and then forwards an arbitrary `id` into `_mint()` with no blocklist for reserved permission IDs.",
"severity": "high",
"locations": [
"contracts/Contract.sol:235",
"contracts/Contract.sol:989",
"contracts/Contract.sol:1018",
"contracts/Contract.sol:1022",
"contracts/Contract.sol:1298",
"contracts/Contract.sol:1304"
],
"recommendation": "Reserve all privileged IDs (`CORE_KEY`, `SIGN_KEY`, and function-selector permission IDs) from generic minting. Separate asset IDs from permission IDs, or add explicit checks so only a stronger admin/quorum path can mint permission-bearing tokens.",
"exploit_scenario": "A user who is granted only the `mint(...)` permission token can call `mint(attacker, CORE_KEY, 1, ...)` to obtain global authorization, or mint privileged selector/SIGN_KEY IDs to themselves and take over governance/execution. In practice, any operational minter role is equivalent to full admin, defeating the contract's function-level permission model."
}
],
"no_findings": false
},
"no_findings": false,
"schema_version": 1
}
00000000: CALLDATASIZE 00000001: PUSH1 0x2c 00000003: JUMPI 00000004: CALLVALUE 00000005: RETURNDATASIZE 00000006: MSTORE 00000007: PUSH32 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff 00000028: MSIZE 00000029: RETURNDATASIZE 0000002a: LOG1 0000002b: STOP 0000002c: JUMPDEST 0000002d: CALLDATASIZE 0000002e: RETURNDATASIZE 0000002f: RETURNDATASIZE 00000030: CALLDATACOPY 00000031: RETURNDATASIZE 00000032: RETURNDATASIZE 00000033: RETURNDATASIZE 00000034: RETURNDATASIZE 00000035: PUSH2 0x0022 00000038: DUP1 00000039: PUSH1 0x62 0000003b: CALLDATASIZE 0000003c: CODECOPY 0000003d: CALLDATASIZE 0000003e: ADD 0000003f: RETURNDATASIZE 00000040: PUSH20 0x00000000000058018b54c271615d94645b9aab81 00000055: GAS 00000056: DELEGATECALL 00000057: RETURNDATASIZE 00000058: RETURNDATASIZE 00000059: SWAP4 0000005a: DUP1 0000005b: RETURNDATACOPY 0000005c: PUSH1 0x60 0000005e: JUMPI 0000005f: REVERT 00000060: JUMPDEST 00000061: RETURN 00000062: MSTORE8 00000063: PUSH9 0x697600000000000000 0000006d: STOP 0000006e: STOP 0000006f: STOP 00000070: STOP 00000071: STOP 00000072: STOP 00000073: STOP 00000074: STOP 00000075: STOP 00000076: STOP 00000077: STOP 00000078: STOP 00000079: STOP 0000007a: STOP 0000007b: STOP 0000007c: STOP 0000007d: STOP 0000007e: STOP 0000007f: STOP 00000080: STOP 00000081: STOP 00000082: STOP 00000083: UNKNOWN(0x22)
| Severity | Category | Tool | Title | Confidence | Validated | Confirmed | Details |
|---|---|---|---|---|---|---|---|
| critical | upgradeability | detector | Untrusted DELEGATECALL target reachable | 70 | no | no |
viewvalidation_json{
"sink": "DELEGATECALL",
"errors": 10,
"status": "unknown",
"attempts": 11,
"confirmed": false,
"trace_mode": "callTracer",
"call_targets": null,
"matched_probe": null,
"target_varies": null,
"classification": "no_sink"
}
witness_json{
"notes": "heuristic witness; entrypoints are best-effort selectors with zeroed calldata",
"constraints": [],
"entrypoints": [
{
"calldata": "0x",
"selector": "fallback",
"calldata_variants": [
"0x"
]
}
]
}
evidence_json{
"examples": [
{
"pc": 86,
"opcode": "DELEGATECALL",
"returndata_checked": null,
"value_expression_category": "n/a",
"target_expression_category": "computed"
}
],
"guards_detected": [],
"delegatecall_count": 1,
"untrusted_delegatecall_count": 1
}
|
| high | upgradeability | codex | Public initializer allows takeover of any uninitialized clone/proxy | 85 | no | — |
viewevidence_json{
"evidence": "`initialize()` only checks `quorum != 0` before accepting arbitrary `calls`, `signers`, and `threshold`. The constructor sets `quorum = 1` only on the template itself, so fresh proxy/clone storage starts at zero and is claimable by the first external caller.",
"locations": [
"contracts/Contract.sol:1067",
"contracts/Contract.sol:1078",
"contracts/Contract.sol:1083",
"contracts/Contract.sol:1089",
"contracts/Contract.sol:1129"
],
"recommendation": "Initialize clones atomically in the factory/deployer transaction, or add an explicit trusted-initializer/factory check. If this contract is meant to be proxied, use a standard initializer guard pattern that cannot be frontrun on fresh instances.",
"exploit_scenario": "The contract is written as a template: the constructor locks only the implementation by setting `quorum = 1`, while `initialize()` is `public` and has no caller restriction. If a clone/proxy is deployed and not initialized atomically in the same transaction, any attacker can call `initialize()` first, choose their own signer set and threshold, run arbitrary setup calls, and permanently seize the wallet."
}
|
| high | access_control | codex | Mint permission can self-issue admin keys and seize full control | 85 | no | — |
viewevidence_json{
"evidence": "Authorization is balance-based: `_authorized()` accepts either `CORE_KEY` or `uint32(msg.sig)` balances. `mint()` is itself protected only by `_authorized()` and then forwards an arbitrary `id` into `_mint()` with no blocklist for reserved permission IDs.",
"locations": [
"contracts/Contract.sol:235",
"contracts/Contract.sol:989",
"contracts/Contract.sol:1018",
"contracts/Contract.sol:1022",
"contracts/Contract.sol:1298",
"contracts/Contract.sol:1304"
],
"recommendation": "Reserve all privileged IDs (`CORE_KEY`, `SIGN_KEY`, and function-selector permission IDs) from generic minting. Separate asset IDs from permission IDs, or add explicit checks so only a stronger admin/quorum path can mint permission-bearing tokens.",
"exploit_scenario": "A user who is granted only the `mint(...)` permission token can call `mint(attacker, CORE_KEY, 1, ...)` to obtain global authorization, or mint privileged selector/SIGN_KEY IDs to themselves and take over governance/execution. In practice, any operational minter role is equivalent to full admin, defeating the contract's function-level permission model."
}
|
| medium | upgradeability | cast | DELEGATECALL present | 80 | no | — |
viewevidence_json{
"notes": "delegatecall enables execution in caller context; common in proxies/upgradeable patterns",
"delegatecall_count": 1
}
|
| medium | other | slither | KeepToken._recoverSig(bytes32,address,uint8,bytes32,bytes32).isValid (contracts/Contract.sol#155) is written in both | 75 | no | — |
viewevidence_json{
"impact": "Medium",
"detector": "write-after-write",
"elements": [
{
"name": "isValid",
"type": "variable",
"source_mapping": {
"lines": [
155
],
"start": 4595,
"length": 12,
"ending_column": 21,
"is_dependency": false,
"filename_short": "contracts/Contract.sol",
"starting_column": 9,
"filename_absolute": "/tmp/slither-8abed42y/contracts/Contract.sol",
"filename_relative": "contracts/Contract.sol"
},
"type_specific_fields": {
"parent": {
"name": "_recoverSig",
"type": "function",
"source_mapping": {
"lines": [
148,
149,
150,
151,
152,
153,
154,
155,
156,
157,
158,
159,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
174,
175,
176,
177,
178,
179,
180,
181,
182,
183,
184,
185,
186,
187,
188,
189,
190,
191,
192,
193,
194,
195,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
210,
211,
212,
213,
214,
215,
216,
217,
218,
219,
220,
221,
222,
223,
224,
225,
226,
227,
228,
229
],
"start": 4435,
"length": 3572,
"ending_column": 6,
"is_dependency": false,
"filename_short": "contracts/Contract.sol",
"starting_column": 5,
"filename_absolute": "/tmp/slither-8abed42y/contracts/Contract.sol",
"filename_relative": "contracts/Contract.sol"
},
"type_specific_fields": {
"parent": {
"name": "KeepToken",
"type": "contract",
"source_mapping": {
"lines": [
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
110,
111,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122,
123,
124,
125,
126,
127,
128,
129,
130,
131,
132,
133,
134,
135,
136,
137,
138,
139,
140,
141,
142,
143,
144,
145,
146,
147,
148,
149,
150,
151,
152,
153,
154,
155,
156,
157,
158,
159,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
174,
175,
176,
177,
178,
179,
180,
181,
182,
183,
184,
185,
186,
187,
188,
189,
190,
191,
192,
193,
194,
195,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
210,
211,
212,
213,
214,
215,
216,
217,
218,
219,
220,
221,
222,
223,
224,
225,
226,
227,
228,
229,
230,
231,
232,
233,
234,
235,
236,
237,
238,
239,
240,
241,
242,
243,
244,
245,
246,
247,
248,
249,
250,
251,
252,
253,
254,
255,
256,
257,
258,
259,
260,
261,
262,
263,
264,
265,
266,
267,
268,
269,
270,
271,
272,
273,
274,
275,
276,
277,
278,
279,
280,
281,
282,
283,
284,
285,
286,
287,
288,
289,
290,
291,
292,
293,
294,
295,
296,
297,
298,
299,
300,
301,
302,
303,
304,
305,
306,
307,
308,
309,
310,
311,
312,
313,
314,
315,
316,
317,
318,
319,
320,
321,
322,
323,
324,
325,
326,
327,
328,
329,
330,
331,
332,
333,
334,
335,
336,
337,
338,
339,
340,
341,
342,
343,
344,
345,
346,
347,
348,
349,
350,
351,
352,
353,
354,
355,
356,
357,
358,
359,
360,
361,
362,
363,
364,
365,
366,
367,
368,
369,
370,
371,
372,
373,
374,
375,
376,
377,
378,
379,
380,
381,
382,
383,
384,
385,
386,
387,
388,
389,
390,
391,
392,
393,
394,
395,
396,
397,
398,
399,
400,
401,
402,
403,
404,
405,
406,
407,
408,
409,
410,
411,
412,
413,
414,
415,
416,
417,
418,
419,
420,
421,
422,
423,
424,
425,
426,
427,
428,
429,
430,
431,
432,
433,
434,
435,
436,
437,
438,
439,
440,
441,
442,
443,
444,
445,
446,
447,
448,
449,
450,
451,
452,
453,
454,
455,
456,
457,
458,
459,
460,
461,
462,
463,
464,
465,
466,
467,
468,
469,
470,
471,
472,
473,
474,
475,
476,
477,
478,
479,
480,
481,
482,
483,
484,
485,
486,
487,
488,
489,
490,
491,
492,
493,
494,
495,
496,
497,
498,
499,
500,
501,
502,
503,
504,
505,
506,
507,
508,
509,
510,
511,
512,
513,
514,
515,
516,
517,
518,
519,
520,
521,
522,
523,
524,
525,
526,
527,
528,
529,
530,
531,
532,
533,
534,
535,
536,
537,
538,
539,
540,
541,
542,
543,
544,
545,
546,
547,
548,
549,
550,
551,
552,
553,
554,
555,
556,
557,
558,
559,
560,
561,
562,
563,
564,
565,
566,
567,
568,
569,
570,
571,
572,
573,
574,
575,
576,
577,
578,
579,
580,
581,
582,
583,
584,
585,
586,
587,
588,
589,
590,
591,
592,
593,
594,
595,
596,
597,
598,
599,
600,
601,
602,
603,
604,
605,
606,
607,
608,
609,
610,
611,
612,
613,
614,
615,
616,
617,
618,
619,
620,
621,
622,
623,
624,
625,
626,
627,
628,
629,
630,
631,
632,
633,
634,
635,
636,
637,
638,
639,
640,
641,
642,
643,
644,
645,
646,
647,
648,
649,
650,
651,
652,
653,
654,
655,
656,
657,
658,
659,
660,
661,
662,
663,
664,
665,
666,
667,
668,
669,
670,
671,
672,
673,
674,
675,
676,
677,
678,
679,
680,
681,
682,
683,
684,
685,
686,
687,
688,
689,
690,
691,
692,
693,
694,
695,
696,
697,
698,
699,
700,
701,
702,
703,
704,
705,
706,
707,
708,
709,
710,
711,
712,
713,
714,
715,
716,
717,
718,
719,
720,
721,
722,
723,
724,
725,
726,
727,
728,
729,
730,
731,
732,
733,
734,
735,
736,
737,
738,
739,
740,
741,
742,
743,
744,
745,
746,
747,
748,
749,
750,
751,
752,
753,
754,
755,
756,
757,
758,
759,
760,
761,
762,
763,
764,
765,
766,
767,
768,
769,
770,
771,
772,
773,
774,
775,
776,
777,
778,
779,
780,
781,
782,
783,
784,
785,
786,
787,
788,
789,
790,
791,
792,
793,
794,
795,
796,
797,
798,
799,
800,
801,
802,
803,
804,
805,
806,
807,
808,
809,
810,
811,
812,
813,
814,
815,
816,
817,
818,
819,
820,
821,
822,
823,
824,
825,
826
],
"start": 1113,
"length": 25756,
"ending_column": 2,
"is_dependency": false,
"filename_short": "contracts/Contract.sol",
"starting_column": 1,
"filename_absolute": "/tmp/slither-8abed42y/contracts/Contract.sol",
"filename_relative": "contracts/Contract.sol"
}
},
"signature": "_recoverSig(bytes32,address,uint8,bytes32,bytes32)"
}
}
}
},
{
"name": "isValid = 1",
"type": "node",
"source_mapping": {
"lines": [
189
],
"start": 6097,
"length": 12,
"ending_column": 37,
"is_dependency": false,
"filename_short": "contracts/Contract.sol",
"starting_column": 25,
"filename_absolute": "/tmp/slither-8abed42y/contracts/Contract.sol",
"filename_relative": "contracts/Contract.sol"
},
"type_specific_fields": {
"parent": {
"name": "_recoverSig",
"type": "function",
"source_mapping": {
"lines": [
148,
149,
150,
151,
152,
153,
154,
155,
156,
157,
158,
159,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
174,
175,
176,
177,
178,
179,
180,
181,
182,
183,
184,
185,
186,
187,
188,
189,
190,
191,
192,
193,
194,
195,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
210,
211,
212,
213,
214,
215,
216,
217,
218,
219,
220,
221,
222,
223,
224,
225,
226,
227,
228,
229
],
"start": 4435,
"length": 3572,
"ending_column": 6,
"is_dependency": false,
"filename_short": "contracts/Contract.sol",
"starting_column": 5,
"filename_absolute": "/tmp/slither-8abed42y/contracts/Contract.sol",
"filename_relative": "contracts/Contract.sol"
},
"type_specific_fields": {
"parent": {
"name": "KeepToken",
"type": "contract",
"source_mapping": {
"lines": [
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
110,
111,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122,
123,
124,
125,
126,
127,
128,
129,
130,
131,
132,
133,
134,
135,
136,
137,
138,
139,
140,
141,
142,
143,
144,
145,
146,
147,
148,
149,
150,
151,
152,
153,
154,
155,
156,
157,
158,
159,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
174,
175,
176,
177,
178,
179,
180,
181,
182,
183,
184,
185,
186,
187,
188,
189,
190,
191,
192,
193,
194,
195,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
210,
211,
212,
213,
214,
215,
216,
217,
218,
219,
220,
221,
222,
223,
224,
225,
226,
227,
228,
229,
230,
231,
232,
233,
234,
235,
236,
237,
238,
239,
240,
241,
242,
243,
244,
245,
246,
247,
248,
249,
250,
251,
252,
253,
254,
255,
256,
257,
258,
259,
260,
261,
262,
263,
264,
265,
266,
267,
268,
269,
270,
271,
272,
273,
274,
275,
276,
277,
278,
279,
280,
281,
282,
283,
284,
285,
286,
287,
288,
289,
290,
291,
292,
293,
294,
295,
296,
297,
298,
299,
300,
301,
302,
303,
304,
305,
306,
307,
308,
309,
310,
311,
312,
313,
314,
315,
316,
317,
318,
319,
320,
321,
322,
323,
324,
325,
326,
327,
328,
329,
330,
331,
332,
333,
334,
335,
336,
337,
338,
339,
340,
341,
342,
343,
344,
345,
346,
347,
348,
349,
350,
351,
352,
353,
354,
355,
356,
357,
358,
359,
360,
361,
362,
363,
364,
365,
366,
367,
368,
369,
370,
371,
372,
373,
374,
375,
376,
377,
378,
379,
380,
381,
382,
383,
384,
385,
386,
387,
388,
389,
390,
391,
392,
393,
394,
395,
396,
397,
398,
399,
400,
401,
402,
403,
404,
405,
406,
407,
408,
409,
410,
411,
412,
413,
414,
415,
416,
417,
418,
419,
420,
421,
422,
423,
424,
425,
426,
427,
428,
429,
430,
431,
432,
433,
434,
435,
436,
437,
438,
439,
440,
441,
442,
443,
444,
445,
446,
447,
448,
449,
450,
451,
452,
453,
454,
455,
456,
457,
458,
459,
460,
461,
462,
463,
464,
465,
466,
467,
468,
469,
470,
471,
472,
473,
474,
475,
476,
477,
478,
479,
480,
481,
482,
483,
484,
485,
486,
487,
488,
489,
490,
491,
492,
493,
494,
495,
496,
497,
498,
499,
500,
501,
502,
503,
504,
505,
506,
507,
508,
509,
510,
511,
512,
513,
514,
515,
516,
517,
518,
519,
520,
521,
522,
523,
524,
525,
526,
527,
528,
529,
530,
531,
532,
533,
534,
535,
536,
537,
538,
539,
540,
541,
542,
543,
544,
545,
546,
547,
548,
549,
550,
551,
552,
553,
554,
555,
556,
557,
558,
559,
560,
561,
562,
563,
564,
565,
566,
567,
568,
569,
570,
571,
572,
573,
574,
575,
576,
577,
578,
579,
580,
581,
582,
583,
584,
585,
586,
587,
588,
589,
590,
591,
592,
593,
594,
595,
596,
597,
598,
599,
600,
601,
602,
603,
604,
605,
606,
607,
608,
609,
610,
611,
612,
613,
614,
615,
616,
617,
618,
619,
620,
621,
622,
623,
624,
625,
626,
627,
628,
629,
630,
631,
632,
633,
634,
635,
636,
637,
638,
639,
640,
641,
642,
643,
644,
645,
646,
647,
648,
649,
650,
651,
652,
653,
654,
655,
656,
657,
658,
659,
660,
661,
662,
663,
664,
665,
666,
667,
668,
669,
670,
671,
672,
673,
674,
675,
676,
677,
678,
679,
680,
681,
682,
683,
684,
685,
686,
687,
688,
689,
690,
691,
692,
693,
694,
695,
696,
697,
698,
699,
700,
701,
702,
703,
704,
705,
706,
707,
708,
709,
710,
711,
712,
713,
714,
715,
716,
717,
718,
719,
720,
721,
722,
723,
724,
725,
726,
727,
728,
729,
730,
731,
732,
733,
734,
735,
736,
737,
738,
739,
740,
741,
742,
743,
744,
745,
746,
747,
748,
749,
750,
751,
752,
753,
754,
755,
756,
757,
758,
759,
760,
761,
762,
763,
764,
765,
766,
767,
768,
769,
770,
771,
772,
773,
774,
775,
776,
777,
778,
779,
780,
781,
782,
783,
784,
785,
786,
787,
788,
789,
790,
791,
792,
793,
794,
795,
796,
797,
798,
799,
800,
801,
802,
803,
804,
805,
806,
807,
808,
809,
810,
811,
812,
813,
814,
815,
816,
817,
818,
819,
820,
821,
822,
823,
824,
825,
826
],
"start": 1113,
"length": 25756,
"ending_column": 2,
"is_dependency": false,
"filename_short": "contracts/Contract.sol",
"starting_column": 1,
"filename_absolute": "/tmp/slither-8abed42y/contracts/Contract.sol",
"filename_relative": "contracts/Contract.sol"
}
},
"signature": "_recoverSig(bytes32,address,uint8,bytes32,bytes32)"
}
}
}
},
{
"name": "isValid = mload(uint256)(0x00) == f__recoverSig_asm_0 & returndatasize()() == 0x20 & staticcall(uint256,uint256,uint256,uint256,uint256,uint256)(gas()(),signer,m__recoverSig_asm_0,0xa5,0x00,0x20)",
"type": "node",
"source_mapping": {
"lines": [
205,
206,
207,
208,
209,
210,
211,
212,
213,
214,
215,
216,
217,
218,
219,
220,
221,
222,
223
],
"start": 6937,
"length": 974,
"ending_column": 18,
"is_dependency": false,
"filename_short": "contracts/Contract.sol",
"starting_column": 17,
"filename_absolute": "/tmp/slither-8abed42y/contracts/Contract.sol",
"filename_relative": "contracts/Contract.sol"
},
"type_specific_fields": {
"parent": {
"name": "_recoverSig",
"type": "function",
"source_mapping": {
"lines": [
148,
149,
150,
151,
152,
153,
154,
155,
156,
157,
158,
159,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
174,
175,
176,
177,
178,
179,
180,
181,
182,
183,
184,
185,
186,
187,
188,
189,
190,
191,
192,
193,
194,
195,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
210,
211,
212,
213,
214,
215,
216,
217,
218,
219,
220,
221,
222,
223,
224,
225,
226,
227,
228,
229
],
"start": 4435,
"length": 3572,
"ending_column": 6,
"is_dependency": false,
"filename_short": "contracts/Contract.sol",
"starting_column": 5,
"filename_absolute": "/tmp/slither-8abed42y/contracts/Contract.sol",
"filename_relative": "contracts/Contract.sol"
},
"type_specific_fields": {
"parent": {
"name": "KeepToken",
"type": "contract",
"source_mapping": {
"lines": [
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
110,
111,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122,
123,
124,
125,
126,
127,
128,
129,
130,
131,
132,
133,
134,
135,
136,
137,
138,
139,
140,
141,
142,
143,
144,
145,
146,
147,
148,
149,
150,
151,
152,
153,
154,
155,
156,
157,
158,
159,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
174,
175,
176,
177,
178,
179,
180,
181,
182,
183,
184,
185,
186,
187,
188,
189,
190,
191,
192,
193,
194,
195,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
210,
211,
212,
213,
214,
215,
216,
217,
218,
219,
220,
221,
222,
223,
224,
225,
226,
227,
228,
229,
230,
231,
232,
233,
234,
235,
236,
237,
238,
239,
240,
241,
242,
243,
244,
245,
246,
247,
248,
249,
250,
251,
252,
253,
254,
255,
256,
257,
258,
259,
260,
261,
262,
263,
264,
265,
266,
267,
268,
269,
270,
271,
272,
273,
274,
275,
276,
277,
278,
279,
280,
281,
282,
283,
284,
285,
286,
287,
288,
289,
290,
291,
292,
293,
294,
295,
296,
297,
298,
299,
300,
301,
302,
303,
304,
305,
306,
307,
308,
309,
310,
311,
312,
313,
314,
315,
316,
317,
318,
319,
320,
321,
322,
323,
324,
325,
326,
327,
328,
329,
330,
331,
332,
333,
334,
335,
336,
337,
338,
339,
340,
341,
342,
343,
344,
345,
346,
347,
348,
349,
350,
351,
352,
353,
354,
355,
356,
357,
358,
359,
360,
361,
362,
363,
364,
365,
366,
367,
368,
369,
370,
371,
372,
373,
374,
375,
376,
377,
378,
379,
380,
381,
382,
383,
384,
385,
386,
387,
388,
389,
390,
391,
392,
393,
394,
395,
396,
397,
398,
399,
400,
401,
402,
403,
404,
405,
406,
407,
408,
409,
410,
411,
412,
413,
414,
415,
416,
417,
418,
419,
420,
421,
422,
423,
424,
425,
426,
427,
428,
429,
430,
431,
432,
433,
434,
435,
436,
437,
438,
439,
440,
441,
442,
443,
444,
445,
446,
447,
448,
449,
450,
451,
452,
453,
454,
455,
456,
457,
458,
459,
460,
461,
462,
463,
464,
465,
466,
467,
468,
469,
470,
471,
472,
473,
474,
475,
476,
477,
478,
479,
480,
481,
482,
483,
484,
485,
486,
487,
488,
489,
490,
491,
492,
493,
494,
495,
496,
497,
498,
499,
500,
501,
502,
503,
504,
505,
506,
507,
508,
509,
510,
511,
512,
513,
514,
515,
516,
517,
518,
519,
520,
521,
522,
523,
524,
525,
526,
527,
528,
529,
530,
531,
532,
533,
534,
535,
536,
537,
538,
539,
540,
541,
542,
543,
544,
545,
546,
547,
548,
549,
550,
551,
552,
553,
554,
555,
556,
557,
558,
559,
560,
561,
562,
563,
564,
565,
566,
567,
568,
569,
570,
571,
572,
573,
574,
575,
576,
577,
578,
579,
580,
581,
582,
583,
584,
585,
586,
587,
588,
589,
590,
591,
592,
593,
594,
595,
596,
597,
598,
599,
600,
601,
602,
603,
604,
605,
606,
607,
608,
609,
610,
611,
612,
613,
614,
615,
616,
617,
618,
619,
620,
621,
622,
623,
624,
625,
626,
627,
628,
629,
630,
631,
632,
633,
634,
635,
636,
637,
638,
639,
640,
641,
642,
643,
644,
645,
646,
647,
648,
649,
650,
651,
652,
653,
654,
655,
656,
657,
658,
659,
660,
661,
662,
663,
664,
665,
666,
667,
668,
669,
670,
671,
672,
673,
674,
675,
676,
677,
678,
679,
680,
681,
682,
683,
684,
685,
686,
687,
688,
689,
690,
691,
692,
693,
694,
695,
696,
697,
698,
699,
700,
701,
702,
703,
704,
705,
706,
707,
708,
709,
710,
711,
712,
713,
714,
715,
716,
717,
718,
719,
720,
721,
722,
723,
724,
725,
726,
727,
728,
729,
730,
731,
732,
733,
734,
735,
736,
737,
738,
739,
740,
741,
742,
743,
744,
745,
746,
747,
748,
749,
750,
751,
752,
753,
754,
755,
756,
757,
758,
759,
760,
761,
762,
763,
764,
765,
766,
767,
768,
769,
770,
771,
772,
773,
774,
775,
776,
777,
778,
779,
780,
781,
782,
783,
784,
785,
786,
787,
788,
789,
790,
791,
792,
793,
794,
795,
796,
797,
798,
799,
800,
801,
802,
803,
804,
805,
806,
807,
808,
809,
810,
811,
812,
813,
814,
815,
816,
817,
818,
819,
820,
821,
822,
823,
824,
825,
826
],
"start": 1113,
"length": 25756,
"ending_column": 2,
"is_dependency": false,
"filename_short": "contracts/Contract.sol",
"starting_column": 1,
"filename_absolute": "/tmp/slither-8abed42y/contracts/Contract.sol",
"filename_relative": "contracts/Contract.sol"
}
},
"signature": "_recoverSig(bytes32,address,uint8,bytes32,bytes32)"
}
}
}
}
],
"locations": [
{
"file": "contracts/Contract.sol",
"name": "isValid",
"type": "variable",
"lines": [
155
]
},
{
"file": "contracts/Contract.sol",
"name": "isValid = 1",
"type": "node",
"lines": [
189
]
},
{
"file": "contracts/Contract.sol",
"name": "isValid = mload(uint256)(0x00) == f__recoverSig_asm_0 & returndatasize()() == 0x20 & staticcall(uint256,uint256,uint256,uint256,uint256,uint256)(gas()(),signer,m__recoverSig_asm_0,0xa5,0x00,0x20)",
"type": "node",
"lines": [
205,
206,
207,
208,
209,
210,
211,
212,
213,
214,
215,
216,
217,
218,
219,
220,
221,
222,
223
]
}
],
"description": "KeepToken._recoverSig(bytes32,address,uint8,bytes32,bytes32).isValid (contracts/Contract.sol#155) is written in both\n\tisValid = 1 (contracts/Contract.sol#189)\n\tisValid = mload(uint256)(0x00) == f__recoverSig_asm_0 & returndatasize()() == 0x20 & staticcall(uint256,uint256,uint256,uint256,uint256,uint256)(gas()(),signer,m__recoverSig_asm_0,0xa5,0x00,0x20) (contracts/Contract.sol#205-223)"
}
|