### Verify OpenZeppelin Contracts Version Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md Checks the installed version of OpenZeppelin contracts using yarn. This is important for ensuring that the latest security patches and features, such as those in `UUPSUpgradeable` and `ERC1967Upgrade`, are included. ```bash yarn install yarn list @openzeppelin/contracts ``` -------------------------------- ### UUPSUpgradeable Contract Override Example (Solidity) Source: https://github.com/jcam1/jpycv2/blob/main/README.md This Solidity code snippet demonstrates how to override the `_authorizeUpgrade` function within an implementation contract for a UUPS upgradeable proxy. It ensures that only the owner can authorize contract upgrades, providing a crucial security measure. This pattern is essential for managing upgradeability in smart contracts. ```Solidity function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} ``` -------------------------------- ### JavaScript: Deploying and Upgrading Proxies with OZ Upgrades Plugin Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md Illustrates how to deploy and upgrade proxy contracts using the OpenZeppelin upgrades plugin for Hardhat. It shows the process of deploying FiatTokenV1 as a proxy and then upgrading it to FiatTokenV2, ensuring upgrade safety. ```javascript const { ethers, upgrades } = require("hardhat"); contract('TestDeploymentAndUpgrade', async function (accounts) { const minter = accounts[6]; const masterMinter = accounts[3]; const pauser = accounts[4]; const blocklister = accounts[5]; it('should do deployment and upgrades', async () => { // Deploying const FiatTokenV1 = await ethers.getContractFactory("FiatTokenV1"); const instance = await upgrades.deployProxy(FiatTokenV1, [ 'JPY Coin', 'JPYC', 'JPY', 18, masterMinter, pauser, blocklister, minter ], { kind: 'uups' }); await instance.deployed(); // Upgrading const FiatTokenV2 = await ethers.getContractFactory("FiatTokenV2"); const upgraded = await upgrades.upgradeProxy(instance.address, FiatTokenV2); }); }); ``` -------------------------------- ### Solidity: FiatTokenV1 Constructor with Initializer Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md Demonstrates the recommended constructor pattern for FiatTokenV1 and FiatTokenV2 contracts using OpenZeppelin's Initializable contract. It highlights the use of the 'initializer' keyword and the 'blocklisted' assignment, emphasizing best practices for upgradeable contracts. ```solidity import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; // TODO: remove bool internal initialized; // TODO: remove initialized = true; contract FiatTokenV1 is Initializable ... { ... /// @custom:oz-upgrades-unsafe-allow constructor constructor() initializer { // so that users won't accidentally send JPYC to the implementation contract blocklisted[address(this)] = true; } } ``` -------------------------------- ### Compare Unsigned Integers with '!= 0' for Gas Savings Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md In Solidity, when using the optimizer and within a `require` statement, comparing an unsigned integer with `!= 0` is more gas-efficient than comparing it with `> 0`. This optimization is particularly relevant for input validation where checking if a value is non-zero is sufficient. ```solidity require(_amount != 0, "FiatToken: mint amount not zero"); ``` -------------------------------- ### Use Pre-increment for Gas Efficiency Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md Pre-increment operators (e.g., `++i`) generally cost less gas than post-increment operators (e.g., `i++`) in many programming contexts, including Solidity. This is because post-increment requires saving the original value before incrementing, whereas pre-increment can modify the value in place. ```solidity uint256 i; // ... ++i; ``` -------------------------------- ### Initialize JPYC Proxy Contract Source: https://context7.com/jcam1/jpycv2/llms.txt Deploys and initializes the JPYC token using an ERC1967 proxy pattern. This involves deploying an implementation contract and then deploying the proxy, providing initialization data to set up the token's name, symbol, decimals, and role assignments. The contract is automatically blocklisted upon initialization to prevent direct interaction. ```solidity // Deploy implementation FiatTokenV1 implementation = new FiatTokenV1(); // Prepare initialization data bytes memory initData = abi.encodeWithSelector( FiatTokenV1.initialize.selector, "JPY Coin", // name "JPYC", // symbol "JPY", // currency 18, // decimals 0x123..., // minterAdmin 0x456..., // pauser 0x789..., // blocklister 0xabc..., // rescuer 0xdef... // owner ); // Deploy proxy with implementation and initialization ERC1967Proxy proxy = new ERC1967Proxy( address(implementation), initData ); // Proxy is now ready to use FiatTokenV1 token = FiatTokenV1(address(proxy)); // State after initialization: // - Contract version: 1 // - Token name, symbol, decimals set // - All roles assigned // - Contract itself is blocklisted (prevents sending to contract) // - DOMAIN_SEPARATOR computed for EIP-712 signatures // Error: "FiatToken: contract is already initialized" if called twice ``` -------------------------------- ### Controller and Minter Management Source: https://context7.com/jcam1/jpycv2/llms.txt APIs for managing controllers and minters, including removing controllers and minters themselves. ```APIDOC ## Remove Controller ### Description Owner removes a controller's access to manage their assigned minter. This action revokes the controller's ability to perform management tasks. ### Method `removeController` (Solidity function) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **controller** (address) - Required - The address of the controller to remove. ### Request Example ```solidity // Example usage within a smart contract address minterAdmin = 0x..."; // Address of the MinterAdmin contract address controllerToRemove = 0x123...; // Assuming 'minterAdmin' is an instance of the MinterAdmin contract and the caller is the owner minterAdmin.removeController(controllerToRemove); ``` ### Response #### Success Response (200) Emits `ControllerRemoved(controller)`. #### Response Example (N/A for Solidity function calls, events are emitted) --- ## Remove Minter ### Description A controller removes their associated minter. This function must be called by the controller itself before the controller is removed from the system. ### Method `removeMinter` (Solidity function) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```solidity // Example usage within a smart contract address minterAdmin = 0x..."; // Address of the MinterAdmin contract // Assuming the caller is a configured controller minterAdmin.removeMinter(); ``` ### Response #### Success Response (200) Emits `MinterRemoved(controller, minter)`. #### Response Example (N/A for Solidity function calls, events are emitted) --- ## Get Worker Address ### Description Retrieves the worker address associated with a given controller. If the controller has been removed or is not set up, this will return the zero address. ### Method `getWorker` (Solidity function) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **controller** (address) - Required - The address of the controller to query. ### Request Example ```solidity // Example usage within a smart contract address minterAdmin = 0x..."; // Address of the MinterAdmin contract address controller = 0x123...; // Assuming 'minterAdmin' is an instance of the MinterAdmin contract address worker = minterAdmin.getWorker(controller); // 'worker' will be the address of the minter or address(0) ``` ### Response #### Success Response (200) - **worker** (address) - The address of the minter managed by the controller, or address(0) if none exists or the controller is removed. #### Response Example ```json { "worker": "0x0000000000000000000000000000000000000000" } ``` ``` -------------------------------- ### ERC1967Proxy.sol: Avoid redundant named returns Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md In ERC1967Proxy.sol, the `_implementation` function uses both named returns and a return statement. This is unnecessary and can be optimized by removing the unused named return variable to save gas and improve clarity. ```solidity function _implementation() internal view virtual override returns (address) { return ERC1967Upgrade._getImplementation(); } ``` -------------------------------- ### UUPSUpgradeable.sol: Use calldata for read-only external arguments Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md The `upgradeToAndCall` function in UUPSUpgradeable.sol uses `bytes memory data` for an external function argument that is only read. This can be optimized by changing it to `bytes calldata data`, which is more gas-efficient as it avoids copying data to memory. ```solidity function upgradeToAndCall(address newImplementation, bytes calldata data) external payable virtual onlyProxy { _authorizeUpgrade(newImplementation); _upgradeToAndCallUUPS(newImplementation, data, true); } ``` -------------------------------- ### Configure Minter Controller Source: https://context7.com/jcam1/jpycv2/llms.txt Manages multiple minter addresses through a MinterAdmin controller. The owner assigns specific controller addresses to manage individual minter addresses. Once configured, a controller can set allowances for their assigned minter. This pattern allows for granular control over token minting operations. ```solidity // Deploy MinterAdmin address fiatTokenAddress = 0x...; // The JPYC token proxy MinterAdmin minterAdmin = new MinterAdmin(fiatTokenAddress); // Owner assigns controller to manage a minter address controller = 0x123...; // Controller address address minter = 0x456...; // Minter address (worker) console.log("Configuring controller..."); // Only owner can configure controllers minterAdmin.configureController(controller, minter); // Emits: ControllerConfigured(controller, minter) // Controller can now manage their assigned minter console.log("Getting worker address..."); address assignedMinter = minterAdmin.getWorker(controller); // Returns: 0x456... (the minter address) // Controller configures minter allowance (only controller can call) uint256 allowance = 1000000 * 10**18; console.log("Configuring minter allowance..."); minterAdmin.configureMinter(allowance); // Called by controller // Emits: MinterConfigured(controller, minter, allowance) // The FiatToken contract is updated via minterAdmin interface ``` -------------------------------- ### Mint New Tokens Source: https://context7.com/jcam1/jpycv2/llms.txt Authorized minters can create new tokens up to their allocated allowance. ```APIDOC ## Mint New Tokens ### Description Authorized minters create new tokens within their allocated allowance. ### Method `mint(address recipient, uint256 amount)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body (Not applicable for Solidity function calls) ### Request Example ```solidity address minter = 0x742d35Cc6634C0532925a3b844Bc454e4438f44e; address recipient = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; uint256 amount = 50000 * 10**18; bool success = fiatToken.mint(recipient, amount); ``` ### Response #### Success Response (200) `bool success`: `true` if the minting was successful. #### Response Example ```solidity true ``` ### Events Emitted - `Mint(address indexed minter, address indexed recipient, uint256 amount)` - `Transfer(address indexed from, address indexed to, uint256 value)` ### Error Cases - `FiatToken: caller is not a minter` - `FiatToken: mint amount exceeds minterAllowance` - `Pausable: paused` - `Blocklistable: account is blocklisted` ``` -------------------------------- ### Update FiatTokenV2 Versioning for EIP712 Compliance Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md Ensures correct versioning for FiatTokenV2, crucial for EIP712 data signing used in EIP2612 and EIP3009. Updates `DOMAIN_SEPARATOR` and `VERSION` to reflect V2, invalidating authorizations for outdated versions. ```javascript DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(name, "2"); VERSION = "2"; ``` -------------------------------- ### Avoid Explicit Default Variable Initialization Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md Explicitly initializing variables with their default values (0 for uint, false for bool, address(0) for address) is an anti-pattern that wastes gas. If a variable is not initialized, it automatically takes its default value. This optimization applies to Solidity smart contracts. ```solidity for (uint256 i; i < numIterations; ++i) { // ... loop body ... } ``` -------------------------------- ### Use `unchecked` block in `_decreaseAllowance()` for Gas Savings Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md The subtraction `allowed[owner][spender] - decrement` in the `_decreaseAllowance` function can be safely performed within an `unchecked` block. The preceding `require` statement (`decrement <= allowed[owner][spender]`) guarantees that the result of the subtraction will not underflow. This optimization bypasses runtime checks, saving gas. ```solidity File: FiatTokenV1.sol 440: function _decreaseAllowance( ... 445: require( 446: decrement <= allowed[owner][spender],//@audit SLOAD 1 447: "ERC20: decreased allowance below zero" 448: ); 449: _approve(owner, spender, allowed[owner][spender] - decrement); //@audit should be unchecked (see L446) //@audit SLOAD 2 450: } ``` -------------------------------- ### Replace `name` with `tokenName` for Gas Savings Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md Replacing the usage of the `name` storage variable with the `tokenName` memory variable in `EIP712.makeDomainSeparator` can save approximately 2 SLOAD operations, leading to significant gas savings. The `name` variable is read twice, while `tokenName` is already in memory. This change avoids unnecessary storage reads. ```solidity File: FiatTokenV1.sol 96: name = tokenName; ... 105: DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(name, "1"); //@audit name SLOAD 106: CHAIN_ID = block.chainid; 107: NAME = name;//@audit name SLOAD with 105: DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(tokenName, "1"); //@audit tokenName MLOAD ... 107: NAME = tokenName;//@audit tokenName MLOAD Additionally, `EIP712.makeDomainSeparator()` signature is as such: File: EIP712.sol 40: function makeDomainSeparator(string memory name, string memory version) ``` -------------------------------- ### Mint New Tokens (Solidity) Source: https://context7.com/jcam1/jpycv2/llms.txt Authorized minters can create new tokens up to their allocated allowance. This function emits 'Mint' and 'Transfer' events, increases the total supply, and decreases the minter's allowance. It includes checks for caller authorization, sufficient allowance, contract pausable state, and blocklisting. ```solidity // Minter mints tokens to recipient address minter = 0x742d35Cc6634C0532925a3b844Bc454e4438f44e; address recipient = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; uint256 amount = 50000 * 10**18; // Minter calls mint (must be configured minter) bool success = fiatToken.mint(recipient, amount); // Returns: true if successful // Emits: Mint(minter, recipient, amount) // Emits: Transfer(address(0), recipient, amount) // Minter allowance decreases by amount minted // Total supply increases by amount minted // Error cases: // - "FiatToken: caller is not a minter" - caller not authorized // - "FiatToken: mint amount exceeds minterAllowance" - insufficient allowance // - "Pausable: paused" - contract paused // - "Blocklistable: account is blocklisted" - minter or recipient blocklisted ``` -------------------------------- ### Use `unchecked` block in `_transfer()` for Gas Savings Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md The subtraction operation `balances[from] = balances[from] - value;` within the `_transfer` function can be safely executed inside an `unchecked` block. The preceding `require` statement (`value <= balances[from]`) ensures that the subtraction will not underflow. This optimization reduces gas costs by bypassing runtime checks. ```solidity File: FiatTokenV1.sol 304: function _transfer( ... 311: require( 312: value <= balances[from],//@audit SLOAD 1 313: "ERC20: transfer amount exceeds balance" 314: ); 315: 316: balances[from] = balances[from] - value; //@audit should be unchecked (see L312) //@audit SLOAD 2 317: balances[to] = balances[to] + value; ``` -------------------------------- ### V2 Allowlist Feature Source: https://context7.com/jcam1/jpycv2/llms.txt Features related to the V2 allowlist, including adding/removing addresses, checking status, and updating the allowlister role. ```APIDOC ## Allowlist High-Value Sender ### Description Allows a specified address to perform transfers and approvals exceeding the high-value limit (100,000 tokens). Addresses not on the allowlist are restricted to transactions below this limit. ### Method `allowlist` (Solidity function) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **userAddress** (address) - Required - The address of the user to add to the allowlist. ### Request Example ```solidity // Example usage within a smart contract address fiatTokenV2 = 0x..."; // Address of the FiatTokenV2 contract address userToAllowlist = 0x123...; // Assuming 'fiatTokenV2' is an instance of the FiatTokenV2 contract and the caller is the allowlister fiatTokenV2.allowlist(userToAllowlist); ``` ### Response #### Success Response (200) Emits `Allowlisted(userAddress)`. #### Response Example (N/A for Solidity function calls, events are emitted) --- ## Check Allowlist Status ### Description Checks if a given address is currently on the allowlist. ### Method `isAllowlisted` (Solidity function) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **userAddress** (address) - Required - The address to check. ### Request Example ```solidity // Example usage within a smart contract address fiatTokenV2 = 0x..."; // Address of the FiatTokenV2 contract address userToCheck = 0x123...; // Assuming 'fiatTokenV2' is an instance of the FiatTokenV2 contract bool isAllowlisted = fiatTokenV2.isAllowlisted(userToCheck); // 'isAllowlisted' will be true if the user is on the allowlist, false otherwise. ``` ### Response #### Success Response (200) - **isAllowlisted** (bool) - `true` if the address is allowlisted, `false` otherwise. #### Response Example ```json { "isAllowlisted": true } ``` --- ## Remove from Allowlist ### Description Removes a previously allowlisted address from the allowlist, restricting them to the standard transaction limits. ### Method `unAllowlist` (Solidity function) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **userAddress** (address) - Required - The address to remove from the allowlist. ### Request Example ```solidity // Example usage within a smart contract address fiatTokenV2 = 0x..."; // Address of the FiatTokenV2 contract address userToRemove = 0x123...; // Assuming 'fiatTokenV2' is an instance of the FiatTokenV2 contract and the caller is the allowlister fiatTokenV2.unAllowlist(userToRemove); ``` ### Response #### Success Response (200) Emits `UnAllowlisted(userAddress)`. #### Response Example (N/A for Solidity function calls, events are emitted) --- ## Update Allowlister Role ### Description Updates the address designated as the allowlister. This role has the permission to add or remove addresses from the allowlist. ### Method `updateAllowlister` (Solidity function) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **newAllowlister** (address) - Required - The new address to be designated as the allowlister. ### Request Example ```solidity // Example usage within a smart contract address fiatTokenV2 = 0x..."; // Address of the FiatTokenV2 contract address newAllowlisterAddress = 0x456...; // Assuming 'fiatTokenV2' is an instance of the FiatTokenV2 contract and the caller is the owner fiatTokenV2.updateAllowlister(newAllowlisterAddress); ``` ### Response #### Success Response (200) Emits `AllowlisterChanged(newAllowlister)`. #### Response Example (N/A for Solidity function calls, events are emitted) --- ## Allowlist Limit Amount ### Description Retrieves the threshold amount that triggers the allowlist requirement for transfers and approvals. ### Method `allowlistLimitAmount` (Solidity function) ### Parameters None ### Request Example ```solidity // Example usage within a smart contract address fiatTokenV2 = 0x..."; // Address of the FiatTokenV2 contract // Assuming 'fiatTokenV2' is an instance of the FiatTokenV2 contract uint256 limit = fiatTokenV2.allowlistLimitAmount(); // 'limit' will be the amount in token units (e.g., 100000 * 10**18) ``` ### Response #### Success Response (200) - **limit** (uint256) - The amount that defines the high-value transaction threshold. #### Response Example ```json { "limit": "100000000000000000000000" } ``` --- ## Error Case: Non-Allowlisted High-Value Transfer ### Description This describes the error that occurs when a user who is not on the allowlist attempts to send or approve an amount exceeding the `allowlistLimitAmount`. ### Method N/A (Describes error condition for `transfer` and `approve` functions) ### Parameters None ### Request Example None ### Response #### Error Response - "FiatToken: account is not allowlisted" #### Response Example (N/A, this is a revert reason in Solidity) ``` -------------------------------- ### Use `unchecked` block in `mint()` for Gas Savings Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md The subtraction operation `minterAllowed[msg.sender] = mintingAllowedAmount - _amount;` in the `mint` function can be safely placed inside an `unchecked` block. This is because the preceding `require` statement (`_amount <= mintingAllowedAmount`) guarantees that `mintingAllowedAmount - _amount` will not underflow. Using `unchecked` can save gas by omitting runtime overflow/underflow checks. ```solidity File: FiatTokenV1.sol 127: function mint(address _to, uint256 _amount) ... 139: require( 140: _amount <= mintingAllowedAmount, ... 146: minterAllowed[msg.sender] = mintingAllowedAmount - _amount;//@audit should be unchecked (see L140) ``` -------------------------------- ### Shorten Revert Strings for Gas Efficiency Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md Long revert strings in smart contracts increase deployment and runtime gas costs. Strings longer than 32 bytes require additional memory operations. Shortening these strings to fit within 32 bytes or using custom errors can significantly reduce gas consumption. ```solidity require(condition, "ShortRevert"); ``` -------------------------------- ### Receive With Authorization (EIP-3009) Source: https://context7.com/jcam1/jpycv2/llms.txt Gasless transfer where the payee must be the transaction caller, preventing front-running. ```APIDOC ## Receive With Authorization (EIP-3009) ### Description Gasless transfer where the payee must be the transaction caller, preventing front-running. ### Method `receiveWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body (Not applicable for Solidity function calls) ### Request Example ```solidity // Similar to transferWithAuthorization but adds front-running protection address from = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; address to = 0x742d35Cc6634C0532925a3b844Bc454e4438f44e; // Must be msg.sender uint256 value = 100 * 10**18; uint256 validAfter = 1640000000; uint256 validBefore = 1640086400; bytes32 nonce = 0xabcd...ef01; // EIP-712 signature (using RECEIVE_WITH_AUTHORIZATION_TYPEHASH) (uint8 v, bytes32 r, bytes32 s) = signReceiveAuthorization(...); // Only the 'to' address can submit this transaction fiatToken.receiveWithAuthorization( from, to, value, validAfter, validBefore, nonce, v, r, s ); ``` ### Response #### Success Response (200) (No explicit return value, transaction success indicates successful transfer) #### Response Example (N/A) ### Events Emitted - `Transfer(address indexed from, address indexed to, uint256 value)` - `AuthorizationUsed(address indexed authorizer, bytes32 nonce)` ### Error Cases - `EIP3009: caller must be the payee` - `EIP3009: invalid signature` - `EIP3009: signature expired` - `EIP3009: nonce already used` - `Pausable: paused` - `Blocklistable: account is blocklisted` ``` -------------------------------- ### Transfer With Authorization (EIP-3009) Source: https://context7.com/jcam1/jpycv2/llms.txt Execute ERC-20 token transfers using off-chain signatures without requiring gas from the token holder. ```APIDOC ## Transfer With Authorization (EIP-3009) ### Description Execute ERC-20 token transfers using off-chain signatures without requiring gas from the token holder. ### Method `transferWithAuthorization(address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body (Not applicable for Solidity function calls) ### Request Example ```solidity // Off-chain: Token holder signs authorization address from = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; address to = 0x742d35Cc6634C0532925a3b844Bc454e4438f44e; uint256 value = 100 * 10**18; uint256 validAfter = 1640000000; uint256 validBefore = 1640086400; bytes32 nonce = 0x1234...5678; // EIP-712 structured data hash bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, keccak256(abi.encode( TRANSFER_WITH_AUTHORIZATION_TYPEHASH, from, to, value, validAfter, validBefore, nonce )) )); // Token holder signs digest (off-chain) (uint8 v, bytes32 r, bytes32 s) = sign(digest, holderPrivateKey); // On-chain: Anyone can submit the signed authorization fiatToken.transferWithAuthorization( from, to, value, validAfter, validBefore, nonce, v, r, s ); ``` ### Response #### Success Response (200) (No explicit return value, transaction success indicates successful transfer) #### Response Example (N/A) ### Events Emitted - `Transfer(address indexed from, address indexed to, uint256 value)` - `AuthorizationUsed(address indexed authorizer, bytes32 nonce)` ### Error Cases - `EIP3009: invalid signature` - `EIP3009: signature expired` - `EIP3009: nonce already used` - `Pausable: paused` - `Blocklistable: account is blocklisted` ``` -------------------------------- ### Upgrade JPYC Proxy Implementation Source: https://context7.com/jcam1/jpycv2/llms.txt Upgrades the implementation contract of a JPYC proxy to a new version while preserving all existing state and storage. The upgrade process is restricted to the contract owner. It supports upgrading directly or upgrading and calling an initialization function in a single transaction. Compatible storage layout is crucial for maintaining state. ```solidity // Deploy new implementation FiatTokenV2 newImplementation = new FiatTokenV2(); // Owner upgrades the proxy (only owner can upgrade) address owner = 0xdef...; address proxyAddress = 0x...; // Current proxy FiatTokenV1 token = FiatTokenV1(proxyAddress); // Upgrade to new implementation console.log("Upgrading implementation..."); token.upgradeTo(address(newImplementation)); // Inherited from UUPSUpgradeable // Protected by _authorizeUpgrade (onlyOwner) // Or upgrade and call initialization in one transaction bytes memory initV2Data = abi.encodeWithSelector( FiatTokenV2.initializeV2.selector ); console.log("Upgrading and calling V2 initialization..."); token.upgradeToAndCall(address(newImplementation), initV2Data); // Emits: Upgraded(newImplementation) // After upgrade: // - Proxy address unchanged // - All balances, allowances, and state preserved // - New functions from V2 available // - Storage layout must be compatible (use __gap for safety) // V2 adds allowlist feature with 100k token limit // Addresses sending/approving >100k tokens must be allowlisted ``` -------------------------------- ### Transfer With Authorization (EIP-3009) (Solidity) Source: https://context7.com/jcam1/jpycv2/llms.txt Enables token transfers using off-chain signatures, eliminating gas costs for the token holder. This involves an off-chain signing process by the token holder and an on-chain submission by anyone. It emits 'Transfer' and 'AuthorizationUsed' events and tracks used authorizations. ```solidity // Off-chain: Token holder signs authorization // Parameters: address from = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; // Token holder address to = 0x742d35Cc6634C0532925a3b844Bc454e4438f44e; // Recipient uint256 value = 100 * 10**18; uint256 validAfter = 1640000000; // Unix timestamp uint256 validBefore = 1640086400; // Unix timestamp (24 hours later) bytes32 nonce = 0x1234...5678; // Random 32-byte nonce // EIP-712 structured data hash bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, keccak256(abi.encode( TRANSFER_WITH_AUTHORIZATION_TYPEHASH, from, to, value, validAfter, validBefore, nonce )) )); // Token holder signs digest (off-chain) (uint8 v, bytes32 r, bytes32 s) = sign(digest, holderPrivateKey); // On-chain: Anyone can submit the signed authorization fiatToken.transferWithAuthorization( from, to, value, validAfter, validBefore, nonce, v, r, s ); // Emits: Transfer(from, to, value) // Emits: AuthorizationUsed(from, nonce) // Check if authorization is used bool used = fiatToken.authorizationState(from, nonce); // Returns: true (nonce now consumed) ``` -------------------------------- ### Set ERC20 Allowances with Off-chain Permits (Solidity) Source: https://context7.com/jcam1/jpycv2/llms.txt Allows token owners to grant allowances to spenders using off-chain signatures, avoiding on-chain 'approve' transactions. This method utilizes EIP-712 for structured data signing and requires a sequential nonce for security. The signed permit can then be submitted on-chain by any party. ```solidity // Off-chain: Token owner signs permit address owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; address spender = 0x742d35Cc6634C0532925a3b844Bc454e4438f44e; uint256 value = 1000 * 10**18; uint256 deadline = 1640086400; // Unix timestamp uint256 nonce = fiatToken.nonces(owner); // Get current nonce (sequential) // EIP-712 structured data bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, keccak256(abi.encode( PERMIT_TYPEHASH, owner, spender, value, nonce, deadline )) )); // Owner signs permit (uint8 v, bytes32 r, bytes32 s) = sign(digest, ownerPrivateKey); // On-chain: Anyone submits the permit fiatToken.permit(owner, spender, value, deadline, v, r, s); // Emits: Approval(owner, spender, value) // Spender can now use the allowance uint256 allowance = fiatToken.allowance(owner, spender); // Returns: 1000 * 10**18 // Note: nonces are sequential (auto-increment) unlike EIP-3009 random nonces uint256 newNonce = fiatToken.nonces(owner); // Returns: previous nonce + 1 ``` -------------------------------- ### EIP3009.sol: Use non-strict inequalities for gas savings Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md The `_requireValidAuthorization` function in EIP3009.sol uses strict inequalities (`>`,`<`) for timestamp comparisons. Changing these to non-strict inequalities (`>=`,`<=`) can save approximately 3 gas per comparison without significantly altering the functionality. ```solidity require( block.timestamp >= validAfter, // Changed from > to >= "FEIP3009: authorization is not yet valid" ); require( block.timestamp <= validBefore, // Changed from < to <= "EIP3009: authorization is expired" ); ``` -------------------------------- ### Burn Tokens Source: https://context7.com/jcam1/jpycv2/llms.txt Minters can destroy their own tokens, reducing the total supply. ```APIDOC ## Burn Tokens ### Description Minters destroy their own tokens, reducing the total supply. ### Method `burn(uint256 burnAmount)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body (Not applicable for Solidity function calls) ### Request Example ```solidity address minter = 0x742d35Cc6634C0532925a3b844Bc454e4438f44e; uint256 burnAmount = 10000 * 10**18; fiatToken.burn(burnAmount); ``` ### Response #### Success Response (200) (No explicit return value, transaction success indicates successful burn) #### Response Example (N/A) ### Events Emitted - `Burn(address indexed burner, uint256 amount)` - `Transfer(address indexed from, address indexed to, uint256 value)` ### Error Cases - `FiatToken: caller is not a minter` - `FiatToken: burn amount exceeds balance` - `Pausable: paused` ``` -------------------------------- ### Emit external variable instead of storage in FiatTokenV2.sol updateWhitelister Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md This code snippet suggests emitting the external parameter `_newWhitelister` directly in the `WhitelisterChanged` event instead of the storage variable `whitelister`. This is a minor optimization and stylistic suggestion. ```Solidity function updateWhitelister(address _newWhitelister) external onlyOwner { require( _newWhitelister != address(0), "Whitelistable: new whitelister is the zero address" ); whitelister = _newWhitelister; emit WhitelisterChanged(_newWhitelister); // Emit the external parameter } ``` -------------------------------- ### Use `unchecked` block in `burn()` for Gas Savings Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md The line `balances[msg.sender] = balance - _amount;` in the `burn` function can be safely executed within an `unchecked` block. The preceding `require` statement (`balance >= _amount`) guarantees that the subtraction will not underflow. Wrapping this in `unchecked` avoids gas costs associated with runtime overflow/underflow checks. ```solidity File: FiatTokenV1.sol 361: function burn(uint256 _amount) ... 369: require(balance >= _amount, "FiatToken: burn amount exceeds balance"); 370: 371: totalSupply_ = totalSupply_ - _amount; 372: balances[msg.sender] = balance - _amount; //@audit should be unchecked (see L369) ``` -------------------------------- ### Query Token Balance and Supply - JPYC v2 Source: https://context7.com/jcam1/jpycv2/llms.txt Allows users to query the token balance of any given address and the total supply of JPYC tokens. The balance is returned in wei, considering the token's decimals (typically 18 for JPYC). The `balanceOf` function takes an address as input, while `totalSupply` returns the overall number of tokens in circulation. ```solidity // Check balance of an address address account = 0x742d35Cc6634C0532925a3b844Bc454e4438f44e; uint256 balance = fiatToken.balanceOf(account); // Returns: balance in wei (e.g., 1000000000000000000 = 1 token with 18 decimals) // Check total supply uint256 supply = fiatToken.totalSupply(); // Returns: total token supply across all addresses // Example: Display balance in human-readable format // balance = 1500000000000000000 (1.5 tokens) // decimals = 18 // Human readable: balance / (10 ** decimals) = 1.5 JPYC ``` -------------------------------- ### Emit `_newMasterMinter` directly in `updateMasterMinter()` Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md In the `updateMasterMinter` function, the `MasterMinterChanged` event is emitted with the storage variable `masterMinter`. It is recommended to emit the new value directly, `_newMasterMinter`, to avoid emitting a storage value. This ensures that the event reflects the intended new value immediately without relying on the storage update. ```solidity File: FiatTokenV1.sol 377: function updateMasterMinter(address _newMasterMinter) external onlyOwner { 378: require( 379: _newMasterMinter != address(0), 380: "FiatToken: new masterMinter is the zero address" 381: ); 382: masterMinter = _newMasterMinter; 383: emit MasterMinterChanged(masterMinter); //@audit emitting storage value 384: } Suggestions: 383: emit MasterMinterChanged(_newMasterMinter); ``` -------------------------------- ### Use `unchecked` block in `transferFrom()` for Gas Savings Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md The subtraction operation `allowed[from][msg.sender] = allowed[from][msg.sender] - value;` in the `transferFrom` function can be safely placed within an `unchecked` block. The `require` statement at L272 (`value <= allowed[from][msg.sender]`) ensures that the subtraction will not result in an underflow. This optimization saves gas by skipping the default overflow/underflow checks. ```solidity File: FiatTokenV1.sol 258: function transferFrom( ... 271: require( 272: value <= allowed[from][msg.sender], //@audit allowed[from][msg.sender] SLOAD 1 273: "ERC20: transfer amount exceeds allowance" 274: ); 275: _transfer(from, to, value); 276: allowed[from][msg.sender] = allowed[from][msg.sender] - value; //@audit should be unchecked (see L272) //@audit allowed[from][msg.sender] SLOAD 2 ``` -------------------------------- ### Receive With Authorization (EIP-3009) (Solidity) Source: https://context7.com/jcam1/jpycv2/llms.txt Facilitates gasless transfers where the transaction caller must be the intended payee, providing front-running protection. Similar to 'transferWithAuthorization', it uses off-chain signatures for on-chain execution but enforces that the submitter is the recipient. ```solidity // Similar to transferWithAuthorization but adds front-running protection address from = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; address to = 0x742d35Cc6634C0532925a3b844Bc454e4438f44e; // Must be msg.sender uint256 value = 100 * 10**18; uint256 validAfter = 1640000000; uint256 validBefore = 1640086400; bytes32 nonce = 0xabcd...ef01; // EIP-712 signature (using RECEIVE_WITH_AUTHORIZATION_TYPEHASH) (uint8 v, bytes32 r, bytes32 s) = signReceiveAuthorization(...); // Only the 'to' address can submit this transaction fiatToken.receiveWithAuthorization( from, to, value, validAfter, validBefore, nonce, v, r, s ); // Requires: msg.sender == to // Error: "EIP3009: caller must be the payee" if msg.sender != to // This prevents relay services from stealing the payment by front-running ``` -------------------------------- ### Blocklistable.sol: Emit updated variable instead of storage Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md In the `updateBlocklister` function of Blocklistable.sol, the `BlocklisterChanged` event emits the storage variable `blocklister` instead of the new value `_newBlocklister`. Emitting the new value directly can improve clarity and potentially save gas by avoiding an unnecessary SLOAD. ```solidity emit BlocklisterChanged(_newBlocklister); ``` -------------------------------- ### Emit external variable instead of storage in FiatTokenV2.sol Source: https://github.com/jcam1/jpycv2/blob/main/auditReports/Code4rena-Audit-Report-JPYC.md This code snippet suggests emitting the external parameter `_newMasterMinter` directly in the `MasterMinterChanged` event instead of the storage variable `masterMinter`. This is a minor optimization and stylistic suggestion. ```Solidity function updateMasterMinter(address _newMasterMinter) external onlyOwner { require( _newMasterMinter != address(0), "FiatToken: new masterMinter is the zero address" ); masterMinter = _newMasterMinter; emit MasterMinterChanged(_newMasterMinter); // Emit the external parameter } ```