diff --git a/POSITIONS.csv b/POSITIONS.csv new file mode 100644 index 0000000..1591988 --- /dev/null +++ b/POSITIONS.csv @@ -0,0 +1,16 @@ +wallet,amount,duration,transferable,forfeitable +0x1234567890123456789012345678901234567890,1000000000000000000000,31536000,true,false +0x2345678901234567890123456789012345678901,2000000000000000000000,63072000,true,false +0x3456789012345678901234567890123456789012,5000000000000000000000,94608000,false,true +0x4567890123456789012345678901234567890123,10000000000000000000000,126144000,true,false +0x5678901234567890123456789012345678901234,15000000000000000000000,157680000,true,false +0x6789012345678901234567890123456789012345,25000000000000000000000,189216000,false,true +0x7890123456789012345678901234567890123456,30000000000000000000000,220752000,true,false +0x8901234567890123456789012345678901234567,40000000000000000000000,252288000,true,false +0x9012345678901234567890123456789012345678,50000000000000000000000,283824000,false,true +0x0123456789012345678901234567890123456789,60000000000000000000000,315360000,true,false +0x1111111111111111111111111111111111111111,1000000000000000000000,15768000,true,false +0x2222222222222222222222222222222222222222,2000000000000000000000,31536000,true,false +0x3333333333333333333333333333333333333333,3000000000000000000000,47304000,false,true +0x4444444444444444444444444444444444444444,4000000000000000000000,63072000,true,false +0x5555555555555555555555555555555555555555,5000000000000000000000,78840000,true,false diff --git a/README-POSITIONS.md b/README-POSITIONS.md new file mode 100644 index 0000000..6c991e5 --- /dev/null +++ b/README-POSITIONS.md @@ -0,0 +1,116 @@ +# POSITIONS.csv Format Guide + +## ๐Ÿ“‹ **Required CSV Format** + +The `positions-factory.ts` script expects a CSV file with exactly these 5 columns: + +```csv +wallet,amount,duration,transferable,forfeitable +0x1234567890123456789012345678901234567890,1000000000000000000000,31536000,true,false +``` + +## ๐Ÿ”ข **Column Details** + +| Column | Type | Description | Example | +|--------|------|-------------|---------| +| `wallet` | string | Ethereum wallet address | `0x1234567890123456789012345678901234567890` | +| `amount` | string | Amount in **wei** (not HEMI tokens) | `1000000000000000000000` (1000 HEMI) | +| `duration` | string | Lock duration in **seconds** | `31536000` (1 year) | +| `transferable` | string | Whether NFT is transferable | `"true"` or `"false"` | +| `forfeitable` | string | Whether position can be forfeited | `"true"` or `"false"` | + +## ๐Ÿš€ **Quick Start** + +### **Run Position Creation** +```bash +# For local testing +NODE_ENV=local npm run positions:create + +# For production (set POSITION_FACTORY_ADDRESS first) +npm run positions:create +``` + +## ๐Ÿ“Š **Format Examples** + +### **Amount Format (Wei)** +| HEMI Tokens | Wei Amount | +|-------------|------------| +| 1 HEMI | `1000000000000000000` | +| 100 HEMI | `100000000000000000000` | +| 1000 HEMI | `1000000000000000000000` | +| 10000 HEMI | `10000000000000000000000` | + +### **Duration Format (Seconds)** +| Duration | Seconds | +|----------|---------| +| 1 day | `86400` | +| 1 week | `604800` | +| 1 month | `2628000` | +| 6 months | `15768000` | +| 1 year | `31536000` | +| 2 years | `63072000` | +| 3 years | `94608000` | +| 4 years | `126144000` | + +## ๐Ÿ“ **Example CSV** + +```csv +wallet,amount,duration,transferable,forfeitable +0x1234567890123456789012345678901234567890,1000000000000000000000,31536000,true,false +0x2345678901234567890123456789012345678901,2000000000000000000000,63072000,true,false +0x3456789012345678901234567890123456789012,5000000000000000000000,94608000,false,true +0x4567890123456789012345678901234567890123,10000000000000000000000,126144000,true,false +0x5678901234567890123456789012345678901234,15000000000000000000000,157680000,true,false +``` + +## โš ๏ธ **Important Notes** + +1. **Amounts in Wei**: The script expects amounts in wei, not HEMI tokens +2. **Duration in Seconds**: Use seconds, not days or years +3. **Boolean as Strings**: Use `"true"`/`"false"` strings, not boolean values +4. **No Header Row**: The script expects data rows, not headers +5. **Valid Addresses**: All wallet addresses must be valid Ethereum addresses + +## ๐Ÿงช **Local Testing** + +For local testing with Anvil: + +```bash +# Start Anvil with fork +anvil --fork-url https://rpc.hemi.network/rpc --block-time 1 + +# Set environment variable +export NODE_ENV=local + +# Run the script +npm run positions:create +``` + +## ๐Ÿ”„ **Script Workflow** + +The `positions-factory.ts` script will: + +1. **Read CSV** file (`POSITIONS.csv`) +2. **Whitelist** all positions (set status to PENDING) +3. **Approve** HEMI tokens to PositionFactory +4. **Create** each position individually +5. **Remove** the approval after completion + +## ๐Ÿšจ **Common Mistakes** + +1. **Wrong Amount Format**: Using `1000` instead of `1000000000000000000000` +2. **Wrong Duration Format**: Using `365` instead of `31536000` +3. **Invalid Addresses**: Non-valid Ethereum addresses +4. **Wrong Column Order**: Columns must be in exact order +5. **Boolean Format**: Using `true` instead of `"true"` + +## ๐Ÿ’ก **Best Practices** + +1. **Test with small batches** first +2. **Validate addresses** before running +3. **Keep backups** of your CSV files +4. **Use consistent formatting** throughout + +## ๐ŸŽฏ **Ready to Use** + +The `POSITIONS.csv` file is now ready to use with the `positions-factory.ts` script! ๐ŸŽ‰ diff --git a/deploy/02_position_factory.ts b/deploy/02_position_factory.ts new file mode 100644 index 0000000..c80d6a5 --- /dev/null +++ b/deploy/02_position_factory.ts @@ -0,0 +1,26 @@ +import { DeployFunction } from "hardhat-deploy/types"; +import { Addresses } from "../helpers/addresses"; +import { saveForSafeBatchExecution } from "../helpers/safe"; + +const POSITION_FACTORY = "PositionFactory"; + +const func: DeployFunction = async function (hre) { + const { deployments, getNamedAccounts, network } = hre; + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + // Revert if not on chain ID 43111 (Hemi) or 31337 (Localhost) + if (network.config.chainId !== 43111 && network.config.chainId !== 31337) { + throw new Error( + `This deployment script is only for Hemi and Localhost. Current chain ID: ${network.config.chainId}` + ); + } + + await deploy(POSITION_FACTORY, { + from: deployer, + args: [deployer], + log: true + }); +}; + +func.tags = [POSITION_FACTORY]; +export default func; diff --git a/deployments/hemi/PositionFactory.json b/deployments/hemi/PositionFactory.json new file mode 100644 index 0000000..21b5a8d --- /dev/null +++ b/deployments/hemi/PositionFactory.json @@ -0,0 +1,465 @@ +{ + "address": "0xBFf8293Bafb943BE783d01f5C34d5382C2EeD90F", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "owner_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "InvalidArrays", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "duration_", + "type": "uint256" + } + ], + "name": "PositionCreatedAlready", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "user_", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "duration_", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "transferable_", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "forfeitable_", + "type": "bool" + } + ], + "name": "PositionCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "user_", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "duration_", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "enum PositionFactory.Status", + "name": "status", + "type": "uint8" + } + ], + "name": "StatusUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "duration_", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "transferable_", + "type": "bool" + }, + { + "internalType": "bool", + "name": "forfeitable_", + "type": "bool" + } + ], + "name": "create", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "created", + "outputs": [ + { + "internalType": "enum PositionFactory.Status", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "users_", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts_", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "durations_", + "type": "uint256[]" + }, + { + "internalType": "enum PositionFactory.Status", + "name": "status_", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "revertIfCreated_", + "type": "bool" + } + ], + "name": "updateStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0x732990828dfdc9ceeb909c725a0e4afdb7041d5f43f44a81b3efc5ceb5b2efce", + "receipt": { + "to": null, + "from": "0xd30D9A2dF09BA93CB1C68cC179b1B9B5f03AE431", + "contractAddress": "0xBFf8293Bafb943BE783d01f5C34d5382C2EeD90F", + "transactionIndex": 7, + "gasUsed": "679406", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000001000000000000000000000000000000000000020000000000200000000800100000000000100000000000000000420000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000", + "blockHash": "0xca8a52f2679ed8d1669843a452728d69eb8f3b501933b28a2003cd2cfec99f61", + "transactionHash": "0x732990828dfdc9ceeb909c725a0e4afdb7041d5f43f44a81b3efc5ceb5b2efce", + "logs": [ + { + "transactionIndex": 7, + "blockNumber": 2720907, + "transactionHash": "0x732990828dfdc9ceeb909c725a0e4afdb7041d5f43f44a81b3efc5ceb5b2efce", + "address": "0xBFf8293Bafb943BE783d01f5C34d5382C2EeD90F", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000d30d9a2df09ba93cb1c68cc179b1b9b5f03ae431" + ], + "data": "0x", + "logIndex": 16, + "blockHash": "0xca8a52f2679ed8d1669843a452728d69eb8f3b501933b28a2003cd2cfec99f61" + } + ], + "blockNumber": 2720907, + "cumulativeGasUsed": "1493630", + "status": 1, + "byzantium": true + }, + "args": [ + "0xd30D9A2dF09BA93CB1C68cC179b1B9B5f03AE431" + ], + "numDeployments": 1, + "solcInputHash": "ea7d5e52aad94d413c81446e9dba4fc8", + "metadata": "{\"compiler\":{\"version\":\"0.8.29+commit.ab55807c\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"InvalidArrays\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"user_\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount_\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"duration_\",\"type\":\"uint256\"}],\"name\":\"PositionCreatedAlready\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"SafeERC20FailedOperation\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"hash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user_\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount_\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"duration_\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"transferable_\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"forfeitable_\",\"type\":\"bool\"}],\"name\":\"PositionCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"hash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user_\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount_\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"duration_\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"enum PositionFactory.Status\",\"name\":\"status\",\"type\":\"uint8\"}],\"name\":\"StatusUpdated\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"user_\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount_\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"duration_\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"transferable_\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"forfeitable_\",\"type\":\"bool\"}],\"name\":\"create\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"created\",\"outputs\":[{\"internalType\":\"enum PositionFactory.Status\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"users_\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"amounts_\",\"type\":\"uint256[]\"},{\"internalType\":\"uint256[]\",\"name\":\"durations_\",\"type\":\"uint256[]\"},{\"internalType\":\"enum PositionFactory.Status\",\"name\":\"status_\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"revertIfCreated_\",\"type\":\"bool\"}],\"name\":\"updateStatus\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"SafeERC20FailedOperation(address)\":[{\"details\":\"An operation with an ERC-20 token failed.\"}]},\"kind\":\"dev\",\"methods\":{\"acceptOwnership()\":{\"details\":\"The new owner accepts the ownership transfer.\"},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"pendingOwner()\":{\"details\":\"Returns the address of the pending owner.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"transferOwnership(address)\":{\"details\":\"Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. Can only be called by the current owner. Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/utils/PositionFactory.sol\":\"PositionFactory\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {Context} from \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * The initial owner is set to the address provided by the deployer. This can\\n * later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n /**\\n * @dev The caller account is not authorized to perform an operation.\\n */\\n error OwnableUnauthorizedAccount(address account);\\n\\n /**\\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\\n */\\n error OwnableInvalidOwner(address owner);\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\\n */\\n constructor(address initialOwner) {\\n if (initialOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(initialOwner);\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n _checkOwner();\\n _;\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if the sender is not the owner.\\n */\\n function _checkOwner() internal view virtual {\\n if (owner() != _msgSender()) {\\n revert OwnableUnauthorizedAccount(_msgSender());\\n }\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby disabling any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n if (newOwner == address(0)) {\\n revert OwnableInvalidOwner(address(0));\\n }\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0xff6d0bb2e285473e5311d9d3caacb525ae3538a80758c10649a4d61029b017bb\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/Ownable2Step.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {Ownable} from \\\"./Ownable.sol\\\";\\n\\n/**\\n * @dev Contract module which provides access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * This extension of the {Ownable} contract includes a two-step mechanism to transfer\\n * ownership, where the new owner must call {acceptOwnership} in order to replace the\\n * old one. This can help prevent common mistakes, such as transfers of ownership to\\n * incorrect accounts, or to contracts that are unable to interact with the\\n * permission system.\\n *\\n * The initial owner is specified at deployment time in the constructor for `Ownable`. This\\n * can later be changed with {transferOwnership} and {acceptOwnership}.\\n *\\n * This module is used through inheritance. It will make available all functions\\n * from parent (Ownable).\\n */\\nabstract contract Ownable2Step is Ownable {\\n address private _pendingOwner;\\n\\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Returns the address of the pending owner.\\n */\\n function pendingOwner() public view virtual returns (address) {\\n return _pendingOwner;\\n }\\n\\n /**\\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\\n * Can only be called by the current owner.\\n *\\n * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.\\n */\\n function transferOwnership(address newOwner) public virtual override onlyOwner {\\n _pendingOwner = newOwner;\\n emit OwnershipTransferStarted(owner(), newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual override {\\n delete _pendingOwner;\\n super._transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev The new owner accepts the ownership transfer.\\n */\\n function acceptOwnership() public virtual {\\n address sender = _msgSender();\\n if (pendingOwner() != sender) {\\n revert OwnableUnauthorizedAccount(sender);\\n }\\n _transferOwnership(sender);\\n }\\n}\\n\",\"keccak256\":\"0xdcad8898fda432696597752e8ec361b87d85c82cb258115427af006dacf7128c\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC1363.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"./IERC20.sol\\\";\\nimport {IERC165} from \\\"./IERC165.sol\\\";\\n\\n/**\\n * @title IERC1363\\n * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].\\n *\\n * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract\\n * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.\\n */\\ninterface IERC1363 is IERC20, IERC165 {\\n /*\\n * Note: the ERC-165 identifier for this interface is 0xb0202a11.\\n * 0xb0202a11 ===\\n * bytes4(keccak256('transferAndCall(address,uint256)')) ^\\n * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^\\n * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^\\n * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^\\n * bytes4(keccak256('approveAndCall(address,uint256)')) ^\\n * bytes4(keccak256('approveAndCall(address,uint256,bytes)'))\\n */\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferAndCall(address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @param data Additional data with no specified format, sent in call to `to`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param from The address which you want to send tokens from.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferFromAndCall(address from, address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\\n * @param from The address which you want to send tokens from.\\n * @param to The address which you want to transfer to.\\n * @param value The amount of tokens to be transferred.\\n * @param data Additional data with no specified format, sent in call to `to`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\\n * @param spender The address which will spend the funds.\\n * @param value The amount of tokens to be spent.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function approveAndCall(address spender, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\\n * @param spender The address which will spend the funds.\\n * @param value The amount of tokens to be spent.\\n * @param data Additional data with no specified format, sent in call to `spender`.\\n * @return A boolean value indicating whether the operation succeeded unless throwing.\\n */\\n function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);\\n}\\n\",\"keccak256\":\"0x9b6b3e7803bc5f2f8cd7ad57db8ac1def61a9930a5a3107df4882e028a9605d7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC165} from \\\"../utils/introspection/IERC165.sol\\\";\\n\",\"keccak256\":\"0xde7e9fd9aee8d4f40772f96bb3b58836cbc6dfc0227014a061947f8821ea9724\",\"license\":\"MIT\"},\"@openzeppelin/contracts/interfaces/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"../token/ERC20/IERC20.sol\\\";\\n\",\"keccak256\":\"0xce41876e78d1badc0512229b4d14e4daf83bc1003d7f83978d18e0e56f965b9c\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Interface of the ERC-20 standard as defined in the ERC.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the value of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the value of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\\n * caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 value) external returns (bool);\\n\\n /**\\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\\n * allowance mechanism. `value` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 value) external returns (bool);\\n}\\n\",\"keccak256\":\"0xe06a3f08a987af6ad2e1c1e774405d4fe08f1694b67517438b467cecf0da0ef7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC20} from \\\"../IERC20.sol\\\";\\nimport {IERC1363} from \\\"../../../interfaces/IERC1363.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC-20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n /**\\n * @dev An operation with an ERC-20 token failed.\\n */\\n error SafeERC20FailedOperation(address token);\\n\\n /**\\n * @dev Indicates a failed `decreaseAllowance` request.\\n */\\n error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\\n\\n /**\\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n */\\n function safeTransfer(IERC20 token, address to, uint256 value) internal {\\n _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));\\n }\\n\\n /**\\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\\n */\\n function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\\n _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));\\n }\\n\\n /**\\n * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.\\n */\\n function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {\\n return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));\\n }\\n\\n /**\\n * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.\\n */\\n function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {\\n return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));\\n }\\n\\n /**\\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful.\\n *\\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \\\"client\\\"\\n * smart contract uses ERC-7674 to set temporary allowances, then the \\\"client\\\" smart contract should avoid using\\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\\n */\\n function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n forceApprove(token, spender, oldAllowance + value);\\n }\\n\\n /**\\n * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\\n * value, non-reverting calls are assumed to be successful.\\n *\\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \\\"client\\\"\\n * smart contract uses ERC-7674 to set temporary allowances, then the \\\"client\\\" smart contract should avoid using\\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\\n */\\n function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\\n unchecked {\\n uint256 currentAllowance = token.allowance(address(this), spender);\\n if (currentAllowance < requestedDecrease) {\\n revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\\n }\\n forceApprove(token, spender, currentAllowance - requestedDecrease);\\n }\\n }\\n\\n /**\\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\\n * to be set to zero before setting it to a non-zero value, such as USDT.\\n *\\n * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function\\n * only sets the \\\"standard\\\" allowance. Any temporary allowance will remain active, in addition to the value being\\n * set here.\\n */\\n function forceApprove(IERC20 token, address spender, uint256 value) internal {\\n bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));\\n\\n if (!_callOptionalReturnBool(token, approvalCall)) {\\n _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));\\n _callOptionalReturn(token, approvalCall);\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no\\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\\n if (to.code.length == 0) {\\n safeTransfer(token, to, value);\\n } else if (!token.transferAndCall(to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target\\n * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function transferFromAndCallRelaxed(\\n IERC1363 token,\\n address from,\\n address to,\\n uint256 value,\\n bytes memory data\\n ) internal {\\n if (to.code.length == 0) {\\n safeTransferFrom(token, from, to, value);\\n } else if (!token.transferFromAndCall(from, to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no\\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\\n * targeting contracts.\\n *\\n * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.\\n * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}\\n * once without retrying, and relies on the returned value to be true.\\n *\\n * Reverts if the returned value is other than `true`.\\n */\\n function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\\n if (to.code.length == 0) {\\n forceApprove(token, to, value);\\n } else if (!token.approveAndCall(to, value, data)) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n *\\n * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n uint256 returnSize;\\n uint256 returnValue;\\n assembly (\\\"memory-safe\\\") {\\n let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)\\n // bubble errors\\n if iszero(success) {\\n let ptr := mload(0x40)\\n returndatacopy(ptr, 0, returndatasize())\\n revert(ptr, returndatasize())\\n }\\n returnSize := returndatasize()\\n returnValue := mload(0)\\n }\\n\\n if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {\\n revert SafeERC20FailedOperation(address(token));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n *\\n * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.\\n */\\n function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {\\n bool success;\\n uint256 returnSize;\\n uint256 returnValue;\\n assembly (\\\"memory-safe\\\") {\\n success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)\\n returnSize := returndatasize()\\n returnValue := mload(0)\\n }\\n return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);\\n }\\n}\\n\",\"keccak256\":\"0x982c5cb790ab941d1e04f807120a71709d4c313ba0bfc16006447ffbd27fbbd5\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/IERC721.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC165} from \\\"../../utils/introspection/IERC165.sol\\\";\\n\\n/**\\n * @dev Required interface of an ERC-721 compliant contract.\\n */\\ninterface IERC721 is IERC165 {\\n /**\\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\\n */\\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\\n */\\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\\n\\n /**\\n * @dev Returns the number of tokens in ``owner``'s account.\\n */\\n function balanceOf(address owner) external view returns (uint256 balance);\\n\\n /**\\n * @dev Returns the owner of the `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function ownerOf(uint256 tokenId) external view returns (address owner);\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon\\n * a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\\n * are aware of the ERC-721 protocol to prevent tokens from being forever locked.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or\\n * {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon\\n * a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(address from, address to, uint256 tokenId) external;\\n\\n /**\\n * @dev Transfers `tokenId` token from `from` to `to`.\\n *\\n * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721\\n * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must\\n * understand this adds an external call which potentially creates a reentrancy vulnerability.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address from, address to, uint256 tokenId) external;\\n\\n /**\\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\\n * The approval is cleared when the token is transferred.\\n *\\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\\n *\\n * Requirements:\\n *\\n * - The caller must own the token or be an approved operator.\\n * - `tokenId` must exist.\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address to, uint256 tokenId) external;\\n\\n /**\\n * @dev Approve or remove `operator` as an operator for the caller.\\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\\n *\\n * Requirements:\\n *\\n * - The `operator` cannot be the address zero.\\n *\\n * Emits an {ApprovalForAll} event.\\n */\\n function setApprovalForAll(address operator, bool approved) external;\\n\\n /**\\n * @dev Returns the account approved for `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function getApproved(uint256 tokenId) external view returns (address operator);\\n\\n /**\\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\\n *\\n * See {setApprovalForAll}\\n */\\n function isApprovedForAll(address owner, address operator) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x5dc63d1c6a12fe1b17793e1745877b2fcbe1964c3edfd0a482fac21ca8f18261\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Enumerable.sol)\\n\\npragma solidity ^0.8.20;\\n\\nimport {IERC721} from \\\"../IERC721.sol\\\";\\n\\n/**\\n * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension\\n * @dev See https://eips.ethereum.org/EIPS/eip-721\\n */\\ninterface IERC721Enumerable is IERC721 {\\n /**\\n * @dev Returns the total amount of tokens stored by the contract.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns a token ID owned by `owner` at a given `index` of its token list.\\n * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.\\n */\\n function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);\\n\\n /**\\n * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.\\n * Use along with {totalSupply} to enumerate all tokens.\\n */\\n function tokenByIndex(uint256 index) external view returns (uint256);\\n}\\n\",\"keccak256\":\"0x3d6954a93ac198a2ffa384fa58ccf18e7e235263e051a394328002eff4e073de\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n\\n function _contextSuffixLength() internal view virtual returns (uint256) {\\n return 0;\\n }\\n}\\n\",\"keccak256\":\"0x493033a8d1b176a037b2cc6a04dad01a5c157722049bbecf632ca876224dd4b2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)\\n\\npragma solidity ^0.8.20;\\n\\n/**\\n * @dev Interface of the ERC-165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[ERC].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x79796192ec90263f21b464d5bc90b777a525971d3de8232be80d9c4f9fb353b8\",\"license\":\"MIT\"},\"src/interfaces/IRewardDistributor.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.8.29;\\n\\ninterface IRewardDistributor {\\n function updateRewards(uint256 tokenId_) external;\\n}\\n\",\"keccak256\":\"0xdc7f2b68747a29941730579bf0b48595dffb23ede14864884a9fc07edb00fe6e\",\"license\":\"MIT\"},\"src/interfaces/IVeHemi.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.29;\\n\\nimport {IERC721Enumerable} from \\\"@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol\\\";\\nimport {IVeHemiVoteDelegation} from \\\"./IVeHemiVoteDelegation.sol\\\";\\nimport {IRewardDistributor} from \\\"./IRewardDistributor.sol\\\";\\n\\ninterface IVeHemi is IERC721Enumerable {\\n // --- Structs ---\\n struct Point {\\n int128 bias;\\n int128 slope;\\n uint64 timestamp;\\n uint64 blockNumber;\\n uint128 amount;\\n uint256 fixedBias; // for v2+ use\\n }\\n\\n struct UserPoint {\\n Point point;\\n address owner;\\n }\\n\\n struct LockedBalance {\\n int128 amount;\\n uint64 end;\\n }\\n\\n // --- Events ---\\n event Deposit(\\n address indexed provider,\\n uint256 indexed tokenId,\\n uint256 amount,\\n uint256 lockTime,\\n uint256 timestamp\\n );\\n\\n event Withdraw(\\n address indexed provider,\\n uint256 indexed tokenId,\\n uint256 amount,\\n uint256 timestamp\\n );\\n event Lock(\\n address indexed provider,\\n address indexed account,\\n uint256 indexed tokenId,\\n uint256 amount,\\n uint256 start,\\n uint256 lockTime,\\n uint256 extraData,\\n bool transferable,\\n bool forfeitable\\n );\\n event Checkpoint(uint256 epoch, uint256 tokenId, LockedBalance oldLock, LockedBalance newLock);\\n\\n event VoteDelegationUpdated(\\n IVeHemiVoteDelegation indexed oldVoteDelegation,\\n IVeHemiVoteDelegation indexed newVoteDelegation\\n );\\n\\n event RewardDistributorUpdated(\\n IRewardDistributor indexed oldRewardDistributor,\\n IRewardDistributor indexed newRewardDistributor\\n );\\n\\n event ForfeitAdminUpdated(address indexed oldRevokeAdmin, address indexed newRevokeAdmin);\\n\\n // --- External/Public Functions ---\\n function initialize(address owner) external;\\n function checkpoint() external;\\n function createLock(uint256 amount, uint256 lockDuration) external returns (uint256 tokenId);\\n function createLockFor(\\n uint256 amount,\\n uint256 lockDuration,\\n address account,\\n bool transferable,\\n bool revokable\\n ) external returns (uint256 tokenId);\\n function increaseAmount(uint256 tokenId, uint256 amount) external;\\n function increaseUnlockTime(uint256 tokenId, uint256 lockDuration) external;\\n function withdraw(uint256 tokenId) external;\\n function getUserPoint(uint256 tokenId, uint256 epoch) external view returns (UserPoint memory);\\n function getGlobalPoint(uint256 epoch) external view returns (Point memory);\\n function getLockedBalance(uint256 tokenId) external view returns (LockedBalance memory);\\n function totalLocked() external view returns (uint256);\\n function epoch() external view returns (uint256);\\n function userPointEpoch(uint256 tokenId) external view returns (uint256);\\n function balanceOfNFT(uint256 tokenId) external view returns (uint256);\\n function balanceOfNFTAt(uint256 tokenId, uint256 timestamp) external view returns (uint256);\\n function balanceAndOwnerOfNFTAt(\\n uint256 tokenId,\\n uint256 timestamp\\n ) external view returns (uint256, address);\\n function totalVeHemiSupplyAt(uint256 timestamp_) external view returns (uint256);\\n}\\n\",\"keccak256\":\"0x2f9f5bbfe9ee7886883b71b829f027f8230d2ce570f941ebb310c81df57a6317\",\"license\":\"MIT\"},\"src/interfaces/IVeHemiVoteDelegation.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.29;\\n\\ninterface IVeHemiVoteDelegation {\\n struct Delegation {\\n address delegatee;\\n uint48 end;\\n uint96 bias;\\n uint96 amount;\\n uint64 slope;\\n }\\n\\n /// A representation of a delegate and all its delegators at a particular timestamp\\n struct DelegateCheckpoint {\\n uint128 normalizedBias;\\n uint128 fixedBias; // for v2+ use\\n uint128 totalAmount;\\n uint64 normalizedSlope;\\n uint64 timestamp;\\n }\\n\\n /// Represents the total bias, slope, and Hemi amount of all accounts that expire for a specific delegate\\n /// in a particular week\\n struct Expiration {\\n uint96 bias;\\n uint96 amount;\\n uint64 slope;\\n }\\n\\n // Only used in memory\\n struct NormalizedVeHemiLockInfo {\\n uint256 bias;\\n uint256 slope;\\n uint256 amount;\\n uint256 end;\\n }\\n\\n /**\\n * @dev Emitted when an account changes their delegate.\\n */\\n event DelegateChanged(\\n uint256 indexed delegator,\\n address indexed fromDelegatee,\\n address indexed toDelegatee\\n );\\n\\n /**\\n * @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of voting units.\\n */\\n event DelegateVotesChanged(address indexed delegatee, uint256 previousVotes, uint256 newVotes);\\n\\n function delegate(uint256 delegator_, address delegatee_) external;\\n\\n function delegation(uint256 tokenId_) external view returns (Delegation memory);\\n\\n function getVotes(address account_) external view returns (uint256);\\n\\n function getPastVotes(address account_, uint256 timestamp_) external view returns (uint256);\\n}\\n\",\"keccak256\":\"0xf20440ef510e78fb9fe19f5be7b1abefbf3d295c3ef45e453961976b244e546b\",\"license\":\"MIT\"},\"src/utils/PositionFactory.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.8.29;\\n\\nimport {IERC20} from \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport {SafeERC20} from \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport {Ownable, Ownable2Step} from \\\"@openzeppelin/contracts/access/Ownable2Step.sol\\\";\\nimport {IVeHemi} from \\\"../interfaces/IVeHemi.sol\\\";\\n\\ncontract PositionFactory is Ownable2Step {\\n using SafeERC20 for IERC20;\\n\\n IVeHemi constant veHemi = IVeHemi(0x371d3718D5b7F75EAb050FAe6Da7DF3092031c89);\\n IERC20 constant hemi = IERC20(0x99e3dE3817F6081B2568208337ef83295b7f591D);\\n\\n enum Status {\\n NONE,\\n PENDING,\\n CREATED\\n }\\n\\n mapping(bytes32 => Status) public created;\\n\\n event StatusUpdated(\\n bytes32 indexed hash,\\n address indexed user_,\\n uint256 amount_,\\n uint256 duration_,\\n Status status\\n );\\n event PositionCreated(\\n bytes32 indexed hash,\\n address indexed user_,\\n uint256 amount_,\\n uint256 duration_,\\n bool transferable_,\\n bool forfeitable_\\n );\\n\\n error PositionCreatedAlready(address user_, uint256 amount_, uint256 duration_);\\n error InvalidArrays();\\n\\n constructor(address owner_) Ownable(owner_) {}\\n\\n function create(\\n address user_,\\n uint256 amount_,\\n uint256 duration_,\\n bool transferable_,\\n bool forfeitable_\\n ) external {\\n bytes32 _hash = keccak256(abi.encodePacked(user_, amount_, duration_));\\n\\n Status _status = created[_hash];\\n\\n if (_status != Status.PENDING) revert PositionCreatedAlready(user_, amount_, duration_);\\n\\n hemi.safeTransferFrom(msg.sender, address(this), amount_);\\n hemi.forceApprove(address(veHemi), amount_);\\n veHemi.createLockFor(amount_, duration_, user_, transferable_, forfeitable_);\\n\\n created[_hash] = Status.CREATED;\\n\\n emit PositionCreated(_hash, user_, amount_, duration_, transferable_, forfeitable_);\\n }\\n\\n function updateStatus(\\n address[] calldata users_,\\n uint256[] calldata amounts_,\\n uint256[] calldata durations_,\\n Status status_,\\n bool revertIfCreated_\\n ) external onlyOwner {\\n uint256 _length = users_.length;\\n\\n if (_length != amounts_.length || _length != durations_.length) revert InvalidArrays();\\n\\n for (uint256 i; i < _length; ++i) {\\n uint256 _amount = amounts_[i];\\n uint256 _duration = durations_[i];\\n address _user = users_[i];\\n\\n bytes32 _hash = keccak256(abi.encodePacked(_user, _amount, _duration));\\n if (revertIfCreated_ && created[_hash] == Status.CREATED)\\n revert PositionCreatedAlready(_user, _amount, _duration);\\n created[_hash] = status_;\\n\\n emit StatusUpdated(_hash, _user, _amount, _duration, status_);\\n }\\n }\\n}\\n\",\"keccak256\":\"0xfc0bc5d00128e5a72e4373618b2341b750e01e39ef4bc29cd0c28f925aa62507\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x608060405234801561000f575f5ffd5b50604051610bd4380380610bd483398101604081905261002e916100d7565b806001600160a01b03811661005c57604051631e4fbdf760e01b81525f600482015260240160405180910390fd5b6100658161006c565b5050610104565b600180546001600160a01b031916905561008581610088565b50565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b5f602082840312156100e7575f5ffd5b81516001600160a01b03811681146100fd575f5ffd5b9392505050565b610ac3806101115f395ff3fe608060405234801561000f575f5ffd5b5060043610610085575f3560e01c8063e1651c1511610058578063e1651c15146100d7578063e30c3978146100ea578063eaa760c2146100fb578063f2fde38b1461012a575f5ffd5b806353c5d95714610089578063715018a61461009e57806379ba5097146100a65780638da5cb5b146100ae575b5f5ffd5b61009c610097366004610868565b61013d565b005b61009c61034c565b61009c61035f565b5f546001600160a01b03165b6040516001600160a01b0390911681526020015b60405180910390f35b61009c6100e5366004610903565b6103a3565b6001546001600160a01b03166100ba565b61011d6101093660046109c6565b60026020525f908152604090205460ff1681565b6040516100ce9190610a11565b61009c610138366004610a1f565b610587565b6040516bffffffffffffffffffffffff19606087901b16602082015260348101859052605481018490525f9060740160408051601f1981840301815291815281516020928301205f818152600290935291205490915060ff1660018160028111156101aa576101aa6109dd565b146101e65760405163211c23c960e21b81526001600160a01b038816600482015260248101879052604481018690526064015b60405180910390fd5b6102067399e3de3817f6081b2568208337ef83295b7f591d3330896105f7565b6102397399e3de3817f6081b2568208337ef83295b7f591d73371d3718d5b7f75eab050fae6da7df3092031c8988610664565b604051633f64512160e01b815260048101879052602481018690526001600160a01b03881660448201528415156064820152831515608482015273371d3718d5b7f75eab050fae6da7df3092031c8990633f6451219060a4016020604051808303815f875af11580156102ae573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102d29190610a3f565b505f82815260026020818152604092839020805460ff1916909217909155815188815290810187905285151581830152841515606082015290516001600160a01b0389169184917f6b9bb298704ea1d26208f3863114bb934d7ef871cb191c1d1f73c895e31a656b9181900360800190a350505050505050565b6103546106f3565b61035d5f61071f565b565b60015433906001600160a01b031681146103975760405163118cdaa760e01b81526001600160a01b03821660048201526024016101dd565b6103a08161071f565b50565b6103ab6106f3565b8685811415806103bb5750808414155b156103d9576040516314305bff60e31b815260040160405180910390fd5b5f5b8181101561057b575f8888838181106103f6576103f6610a56565b9050602002013590505f87878481811061041257610412610a56565b9050602002013590505f8c8c8581811061042e5761042e610a56565b90506020020160208101906104439190610a1f565b6040516bffffffffffffffffffffffff19606083901b16602082015260348101859052605481018490529091505f906074016040516020818303038152906040528051906020012090508680156104bb57505f8181526002602081905260409091205460ff16818111156104b9576104b96109dd565b145b156104f25760405163211c23c960e21b81526001600160a01b038316600482015260248101859052604481018490526064016101dd565b5f81815260026020819052604090912080548a9260ff19909116906001908490811115610521576105216109dd565b0217905550816001600160a01b0316817f13b2fd9e1ce293f11c0ba6ba7d295ff9a52d3d7b6281b495a0d7e997d8184b3586868c60405161056493929190610a6a565b60405180910390a3505050508060010190506103db565b50505050505050505050565b61058f6106f3565b600180546001600160a01b0383166001600160a01b031990911681179091556105bf5f546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6040516001600160a01b03848116602483015283811660448301526064820183905261065e9186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050610738565b50505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b1790526106b584826107a4565b61065e576040516001600160a01b0384811660248301525f60448301526106e991869182169063095ea7b39060640161062c565b61065e8482610738565b5f546001600160a01b0316331461035d5760405163118cdaa760e01b81523360048201526024016101dd565b600180546001600160a01b03191690556103a0816107ef565b5f5f60205f8451602086015f885af180610757576040513d5f823e3d81fd5b50505f513d9150811561076e57806001141561077b565b6001600160a01b0384163b155b1561065e57604051635274afe760e01b81526001600160a01b03851660048201526024016101dd565b5f5f5f5f60205f8651602088015f8a5af192503d91505f5190508280156107e3575081156107d557806001146107e3565b5f866001600160a01b03163b115b93505050505b92915050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80356001600160a01b0381168114610854575f5ffd5b919050565b80358015158114610854575f5ffd5b5f5f5f5f5f60a0868803121561087c575f5ffd5b6108858661083e565b945060208601359350604086013592506108a160608701610859565b91506108af60808701610859565b90509295509295909350565b5f5f83601f8401126108cb575f5ffd5b50813567ffffffffffffffff8111156108e2575f5ffd5b6020830191508360208260051b85010111156108fc575f5ffd5b9250929050565b5f5f5f5f5f5f5f5f60a0898b03121561091a575f5ffd5b883567ffffffffffffffff811115610930575f5ffd5b61093c8b828c016108bb565b909950975050602089013567ffffffffffffffff81111561095b575f5ffd5b6109678b828c016108bb565b909750955050604089013567ffffffffffffffff811115610986575f5ffd5b6109928b828c016108bb565b9095509350506060890135600381106109a9575f5ffd5b91506109b760808a01610859565b90509295985092959890939650565b5f602082840312156109d6575f5ffd5b5035919050565b634e487b7160e01b5f52602160045260245ffd5b60038110610a0d57634e487b7160e01b5f52602160045260245ffd5b9052565b602081016107e982846109f1565b5f60208284031215610a2f575f5ffd5b610a388261083e565b9392505050565b5f60208284031215610a4f575f5ffd5b5051919050565b634e487b7160e01b5f52603260045260245ffd5b8381526020810183905260608101610a8560408301846109f1565b94935050505056fea26469706673582212203c72861b6233906eeecbe8b8d40952498aa804248df0777f951f9126a72ea0a064736f6c634300081d0033", + "deployedBytecode": "0x608060405234801561000f575f5ffd5b5060043610610085575f3560e01c8063e1651c1511610058578063e1651c15146100d7578063e30c3978146100ea578063eaa760c2146100fb578063f2fde38b1461012a575f5ffd5b806353c5d95714610089578063715018a61461009e57806379ba5097146100a65780638da5cb5b146100ae575b5f5ffd5b61009c610097366004610868565b61013d565b005b61009c61034c565b61009c61035f565b5f546001600160a01b03165b6040516001600160a01b0390911681526020015b60405180910390f35b61009c6100e5366004610903565b6103a3565b6001546001600160a01b03166100ba565b61011d6101093660046109c6565b60026020525f908152604090205460ff1681565b6040516100ce9190610a11565b61009c610138366004610a1f565b610587565b6040516bffffffffffffffffffffffff19606087901b16602082015260348101859052605481018490525f9060740160408051601f1981840301815291815281516020928301205f818152600290935291205490915060ff1660018160028111156101aa576101aa6109dd565b146101e65760405163211c23c960e21b81526001600160a01b038816600482015260248101879052604481018690526064015b60405180910390fd5b6102067399e3de3817f6081b2568208337ef83295b7f591d3330896105f7565b6102397399e3de3817f6081b2568208337ef83295b7f591d73371d3718d5b7f75eab050fae6da7df3092031c8988610664565b604051633f64512160e01b815260048101879052602481018690526001600160a01b03881660448201528415156064820152831515608482015273371d3718d5b7f75eab050fae6da7df3092031c8990633f6451219060a4016020604051808303815f875af11580156102ae573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102d29190610a3f565b505f82815260026020818152604092839020805460ff1916909217909155815188815290810187905285151581830152841515606082015290516001600160a01b0389169184917f6b9bb298704ea1d26208f3863114bb934d7ef871cb191c1d1f73c895e31a656b9181900360800190a350505050505050565b6103546106f3565b61035d5f61071f565b565b60015433906001600160a01b031681146103975760405163118cdaa760e01b81526001600160a01b03821660048201526024016101dd565b6103a08161071f565b50565b6103ab6106f3565b8685811415806103bb5750808414155b156103d9576040516314305bff60e31b815260040160405180910390fd5b5f5b8181101561057b575f8888838181106103f6576103f6610a56565b9050602002013590505f87878481811061041257610412610a56565b9050602002013590505f8c8c8581811061042e5761042e610a56565b90506020020160208101906104439190610a1f565b6040516bffffffffffffffffffffffff19606083901b16602082015260348101859052605481018490529091505f906074016040516020818303038152906040528051906020012090508680156104bb57505f8181526002602081905260409091205460ff16818111156104b9576104b96109dd565b145b156104f25760405163211c23c960e21b81526001600160a01b038316600482015260248101859052604481018490526064016101dd565b5f81815260026020819052604090912080548a9260ff19909116906001908490811115610521576105216109dd565b0217905550816001600160a01b0316817f13b2fd9e1ce293f11c0ba6ba7d295ff9a52d3d7b6281b495a0d7e997d8184b3586868c60405161056493929190610a6a565b60405180910390a3505050508060010190506103db565b50505050505050505050565b61058f6106f3565b600180546001600160a01b0383166001600160a01b031990911681179091556105bf5f546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6040516001600160a01b03848116602483015283811660448301526064820183905261065e9186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050610738565b50505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b1790526106b584826107a4565b61065e576040516001600160a01b0384811660248301525f60448301526106e991869182169063095ea7b39060640161062c565b61065e8482610738565b5f546001600160a01b0316331461035d5760405163118cdaa760e01b81523360048201526024016101dd565b600180546001600160a01b03191690556103a0816107ef565b5f5f60205f8451602086015f885af180610757576040513d5f823e3d81fd5b50505f513d9150811561076e57806001141561077b565b6001600160a01b0384163b155b1561065e57604051635274afe760e01b81526001600160a01b03851660048201526024016101dd565b5f5f5f5f60205f8651602088015f8a5af192503d91505f5190508280156107e3575081156107d557806001146107e3565b5f866001600160a01b03163b115b93505050505b92915050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80356001600160a01b0381168114610854575f5ffd5b919050565b80358015158114610854575f5ffd5b5f5f5f5f5f60a0868803121561087c575f5ffd5b6108858661083e565b945060208601359350604086013592506108a160608701610859565b91506108af60808701610859565b90509295509295909350565b5f5f83601f8401126108cb575f5ffd5b50813567ffffffffffffffff8111156108e2575f5ffd5b6020830191508360208260051b85010111156108fc575f5ffd5b9250929050565b5f5f5f5f5f5f5f5f60a0898b03121561091a575f5ffd5b883567ffffffffffffffff811115610930575f5ffd5b61093c8b828c016108bb565b909950975050602089013567ffffffffffffffff81111561095b575f5ffd5b6109678b828c016108bb565b909750955050604089013567ffffffffffffffff811115610986575f5ffd5b6109928b828c016108bb565b9095509350506060890135600381106109a9575f5ffd5b91506109b760808a01610859565b90509295985092959890939650565b5f602082840312156109d6575f5ffd5b5035919050565b634e487b7160e01b5f52602160045260245ffd5b60038110610a0d57634e487b7160e01b5f52602160045260245ffd5b9052565b602081016107e982846109f1565b5f60208284031215610a2f575f5ffd5b610a388261083e565b9392505050565b5f60208284031215610a4f575f5ffd5b5051919050565b634e487b7160e01b5f52603260045260245ffd5b8381526020810183905260608101610a8560408301846109f1565b94935050505056fea26469706673582212203c72861b6233906eeecbe8b8d40952498aa804248df0777f951f9126a72ea0a064736f6c634300081d0033", + "devdoc": { + "errors": { + "OwnableInvalidOwner(address)": [ + { + "details": "The owner is not a valid owner account. (eg. `address(0)`)" + } + ], + "OwnableUnauthorizedAccount(address)": [ + { + "details": "The caller account is not authorized to perform an operation." + } + ], + "SafeERC20FailedOperation(address)": [ + { + "details": "An operation with an ERC-20 token failed." + } + ] + }, + "kind": "dev", + "methods": { + "acceptOwnership()": { + "details": "The new owner accepts the ownership transfer." + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "pendingOwner()": { + "details": "Returns the address of the pending owner." + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." + }, + "transferOwnership(address)": { + "details": "Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. Can only be called by the current owner. Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 8, + "contract": "src/utils/PositionFactory.sol:PositionFactory", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 156, + "contract": "src/utils/PositionFactory.sol:PositionFactory", + "label": "_pendingOwner", + "offset": 0, + "slot": "1", + "type": "t_address" + }, + { + "astId": 1433, + "contract": "src/utils/PositionFactory.sol:PositionFactory", + "label": "created", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_bytes32,t_enum(Status)1428)" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_enum(Status)1428": { + "encoding": "inplace", + "label": "enum PositionFactory.Status", + "numberOfBytes": "1" + }, + "t_mapping(t_bytes32,t_enum(Status)1428)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => enum PositionFactory.Status)", + "numberOfBytes": "32", + "value": "t_enum(Status)1428" + } + } + } +} \ No newline at end of file diff --git a/deployments/hemi/solcInputs/ea7d5e52aad94d413c81446e9dba4fc8.json b/deployments/hemi/solcInputs/ea7d5e52aad94d413c81446e9dba4fc8.json new file mode 100644 index 0000000..5a60626 --- /dev/null +++ b/deployments/hemi/solcInputs/ea7d5e52aad94d413c81446e9dba4fc8.json @@ -0,0 +1,78 @@ +{ + "language": "Solidity", + "sources": { + "@openzeppelin/contracts/access/Ownable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n constructor(address initialOwner) {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n" + }, + "@openzeppelin/contracts/access/Ownable2Step.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)\n\npragma solidity ^0.8.20;\n\nimport {Ownable} from \"./Ownable.sol\";\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * This extension of the {Ownable} contract includes a two-step mechanism to transfer\n * ownership, where the new owner must call {acceptOwnership} in order to replace the\n * old one. This can help prevent common mistakes, such as transfers of ownership to\n * incorrect accounts, or to contracts that are unable to interact with the\n * permission system.\n *\n * The initial owner is specified at deployment time in the constructor for `Ownable`. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2Step is Ownable {\n address private _pendingOwner;\n\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Returns the address of the pending owner.\n */\n function pendingOwner() public view virtual returns (address) {\n return _pendingOwner;\n }\n\n /**\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n * Can only be called by the current owner.\n *\n * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.\n */\n function transferOwnership(address newOwner) public virtual override onlyOwner {\n _pendingOwner = newOwner;\n emit OwnershipTransferStarted(owner(), newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual override {\n delete _pendingOwner;\n super._transferOwnership(newOwner);\n }\n\n /**\n * @dev The new owner accepts the ownership transfer.\n */\n function acceptOwnership() public virtual {\n address sender = _msgSender();\n if (pendingOwner() != sender) {\n revert OwnableUnauthorizedAccount(sender);\n }\n _transferOwnership(sender);\n }\n}\n" + }, + "@openzeppelin/contracts/interfaces/IERC1363.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @title IERC1363\n * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].\n *\n * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract\n * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.\n */\ninterface IERC1363 is IERC20, IERC165 {\n /*\n * Note: the ERC-165 identifier for this interface is 0xb0202a11.\n * 0xb0202a11 ===\n * bytes4(keccak256('transferAndCall(address,uint256)')) ^\n * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^\n * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^\n * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^\n * bytes4(keccak256('approveAndCall(address,uint256)')) ^\n * bytes4(keccak256('approveAndCall(address,uint256,bytes)'))\n */\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferAndCall(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @param data Additional data with no specified format, sent in call to `to`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param from The address which you want to send tokens from.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferFromAndCall(address from, address to, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param from The address which you want to send tokens from.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @param data Additional data with no specified format, sent in call to `to`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function approveAndCall(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n * @param data Additional data with no specified format, sent in call to `spender`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/interfaces/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"../utils/introspection/IERC165.sol\";\n" + }, + "@openzeppelin/contracts/interfaces/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../token/ERC20/IERC20.sol\";\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC-20 standard as defined in the ERC.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the value of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the value of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\n * allowance mechanism. `value` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\nimport {IERC1363} from \"../../../interfaces/IERC1363.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC-20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n /**\n * @dev An operation with an ERC-20 token failed.\n */\n error SafeERC20FailedOperation(address token);\n\n /**\n * @dev Indicates a failed `decreaseAllowance` request.\n */\n error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\n\n /**\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n */\n function safeTransfer(IERC20 token, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));\n }\n\n /**\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\n */\n function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));\n }\n\n /**\n * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.\n */\n function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {\n return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));\n }\n\n /**\n * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.\n */\n function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {\n return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));\n }\n\n /**\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n *\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n */\n function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\n uint256 oldAllowance = token.allowance(address(this), spender);\n forceApprove(token, spender, oldAllowance + value);\n }\n\n /**\n * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\n * value, non-reverting calls are assumed to be successful.\n *\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n */\n function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\n unchecked {\n uint256 currentAllowance = token.allowance(address(this), spender);\n if (currentAllowance < requestedDecrease) {\n revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\n }\n forceApprove(token, spender, currentAllowance - requestedDecrease);\n }\n }\n\n /**\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\n * to be set to zero before setting it to a non-zero value, such as USDT.\n *\n * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function\n * only sets the \"standard\" allowance. Any temporary allowance will remain active, in addition to the value being\n * set here.\n */\n function forceApprove(IERC20 token, address spender, uint256 value) internal {\n bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));\n\n if (!_callOptionalReturnBool(token, approvalCall)) {\n _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));\n _callOptionalReturn(token, approvalCall);\n }\n }\n\n /**\n * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * Reverts if the returned value is other than `true`.\n */\n function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n if (to.code.length == 0) {\n safeTransfer(token, to, value);\n } else if (!token.transferAndCall(to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target\n * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * Reverts if the returned value is other than `true`.\n */\n function transferFromAndCallRelaxed(\n IERC1363 token,\n address from,\n address to,\n uint256 value,\n bytes memory data\n ) internal {\n if (to.code.length == 0) {\n safeTransferFrom(token, from, to, value);\n } else if (!token.transferFromAndCall(from, to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.\n * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}\n * once without retrying, and relies on the returned value to be true.\n *\n * Reverts if the returned value is other than `true`.\n */\n function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n if (to.code.length == 0) {\n forceApprove(token, to, value);\n } else if (!token.approveAndCall(to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n *\n * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n uint256 returnSize;\n uint256 returnValue;\n assembly (\"memory-safe\") {\n let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)\n // bubble errors\n if iszero(success) {\n let ptr := mload(0x40)\n returndatacopy(ptr, 0, returndatasize())\n revert(ptr, returndatasize())\n }\n returnSize := returndatasize()\n returnValue := mload(0)\n }\n\n if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n *\n * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.\n */\n function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {\n bool success;\n uint256 returnSize;\n uint256 returnValue;\n assembly (\"memory-safe\") {\n success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)\n returnSize := returndatasize()\n returnValue := mload(0)\n }\n return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Enumerable.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC721} from \"../IERC721.sol\";\n\n/**\n * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension\n * @dev See https://eips.ethereum.org/EIPS/eip-721\n */\ninterface IERC721Enumerable is IERC721 {\n /**\n * @dev Returns the total amount of tokens stored by the contract.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns a token ID owned by `owner` at a given `index` of its token list.\n * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.\n */\n function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);\n\n /**\n * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.\n * Use along with {totalSupply} to enumerate all tokens.\n */\n function tokenByIndex(uint256 index) external view returns (uint256);\n}\n" + }, + "@openzeppelin/contracts/token/ERC721/IERC721.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"../../utils/introspection/IERC165.sol\";\n\n/**\n * @dev Required interface of an ERC-721 compliant contract.\n */\ninterface IERC721 is IERC165 {\n /**\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n */\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n */\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n */\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n /**\n * @dev Returns the number of tokens in ``owner``'s account.\n */\n function balanceOf(address owner) external view returns (uint256 balance);\n\n /**\n * @dev Returns the owner of the `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function ownerOf(uint256 tokenId) external view returns (address owner);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon\n * a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n * are aware of the ERC-721 protocol to prevent tokens from being forever locked.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or\n * {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon\n * a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(address from, address to, uint256 tokenId) external;\n\n /**\n * @dev Transfers `tokenId` token from `from` to `to`.\n *\n * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721\n * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must\n * understand this adds an external call which potentially creates a reentrancy vulnerability.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 tokenId) external;\n\n /**\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n * The approval is cleared when the token is transferred.\n *\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n *\n * Requirements:\n *\n * - The caller must own the token or be an approved operator.\n * - `tokenId` must exist.\n *\n * Emits an {Approval} event.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev Approve or remove `operator` as an operator for the caller.\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n *\n * Requirements:\n *\n * - The `operator` cannot be the address zero.\n *\n * Emits an {ApprovalForAll} event.\n */\n function setApprovalForAll(address operator, bool approved) external;\n\n /**\n * @dev Returns the account approved for `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function getApproved(uint256 tokenId) external view returns (address operator);\n\n /**\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n *\n * See {setApprovalForAll}\n */\n function isApprovedForAll(address owner, address operator) external view returns (bool);\n}\n" + }, + "@openzeppelin/contracts/utils/Context.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC-165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[ERC].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" + }, + "src/interfaces/IRewardDistributor.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.29;\n\ninterface IRewardDistributor {\n function updateRewards(uint256 tokenId_) external;\n}\n" + }, + "src/interfaces/IVeHemi.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.29;\n\nimport {IERC721Enumerable} from \"@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol\";\nimport {IVeHemiVoteDelegation} from \"./IVeHemiVoteDelegation.sol\";\nimport {IRewardDistributor} from \"./IRewardDistributor.sol\";\n\ninterface IVeHemi is IERC721Enumerable {\n // --- Structs ---\n struct Point {\n int128 bias;\n int128 slope;\n uint64 timestamp;\n uint64 blockNumber;\n uint128 amount;\n uint256 fixedBias; // for v2+ use\n }\n\n struct UserPoint {\n Point point;\n address owner;\n }\n\n struct LockedBalance {\n int128 amount;\n uint64 end;\n }\n\n // --- Events ---\n event Deposit(\n address indexed provider,\n uint256 indexed tokenId,\n uint256 amount,\n uint256 lockTime,\n uint256 timestamp\n );\n\n event Withdraw(\n address indexed provider,\n uint256 indexed tokenId,\n uint256 amount,\n uint256 timestamp\n );\n event Lock(\n address indexed provider,\n address indexed account,\n uint256 indexed tokenId,\n uint256 amount,\n uint256 start,\n uint256 lockTime,\n uint256 extraData,\n bool transferable,\n bool forfeitable\n );\n event Checkpoint(uint256 epoch, uint256 tokenId, LockedBalance oldLock, LockedBalance newLock);\n\n event VoteDelegationUpdated(\n IVeHemiVoteDelegation indexed oldVoteDelegation,\n IVeHemiVoteDelegation indexed newVoteDelegation\n );\n\n event RewardDistributorUpdated(\n IRewardDistributor indexed oldRewardDistributor,\n IRewardDistributor indexed newRewardDistributor\n );\n\n event ForfeitAdminUpdated(address indexed oldRevokeAdmin, address indexed newRevokeAdmin);\n\n // --- External/Public Functions ---\n function initialize(address owner) external;\n function checkpoint() external;\n function createLock(uint256 amount, uint256 lockDuration) external returns (uint256 tokenId);\n function createLockFor(\n uint256 amount,\n uint256 lockDuration,\n address account,\n bool transferable,\n bool revokable\n ) external returns (uint256 tokenId);\n function increaseAmount(uint256 tokenId, uint256 amount) external;\n function increaseUnlockTime(uint256 tokenId, uint256 lockDuration) external;\n function withdraw(uint256 tokenId) external;\n function getUserPoint(uint256 tokenId, uint256 epoch) external view returns (UserPoint memory);\n function getGlobalPoint(uint256 epoch) external view returns (Point memory);\n function getLockedBalance(uint256 tokenId) external view returns (LockedBalance memory);\n function totalLocked() external view returns (uint256);\n function epoch() external view returns (uint256);\n function userPointEpoch(uint256 tokenId) external view returns (uint256);\n function balanceOfNFT(uint256 tokenId) external view returns (uint256);\n function balanceOfNFTAt(uint256 tokenId, uint256 timestamp) external view returns (uint256);\n function balanceAndOwnerOfNFTAt(\n uint256 tokenId,\n uint256 timestamp\n ) external view returns (uint256, address);\n function totalVeHemiSupplyAt(uint256 timestamp_) external view returns (uint256);\n}\n" + }, + "src/interfaces/IVeHemiVoteDelegation.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.29;\n\ninterface IVeHemiVoteDelegation {\n struct Delegation {\n address delegatee;\n uint48 end;\n uint96 bias;\n uint96 amount;\n uint64 slope;\n }\n\n /// A representation of a delegate and all its delegators at a particular timestamp\n struct DelegateCheckpoint {\n uint128 normalizedBias;\n uint128 fixedBias; // for v2+ use\n uint128 totalAmount;\n uint64 normalizedSlope;\n uint64 timestamp;\n }\n\n /// Represents the total bias, slope, and Hemi amount of all accounts that expire for a specific delegate\n /// in a particular week\n struct Expiration {\n uint96 bias;\n uint96 amount;\n uint64 slope;\n }\n\n // Only used in memory\n struct NormalizedVeHemiLockInfo {\n uint256 bias;\n uint256 slope;\n uint256 amount;\n uint256 end;\n }\n\n /**\n * @dev Emitted when an account changes their delegate.\n */\n event DelegateChanged(\n uint256 indexed delegator,\n address indexed fromDelegatee,\n address indexed toDelegatee\n );\n\n /**\n * @dev Emitted when a token transfer or delegate change results in changes to a delegate's number of voting units.\n */\n event DelegateVotesChanged(address indexed delegatee, uint256 previousVotes, uint256 newVotes);\n\n function delegate(uint256 delegator_, address delegatee_) external;\n\n function delegation(uint256 tokenId_) external view returns (Delegation memory);\n\n function getVotes(address account_) external view returns (uint256);\n\n function getPastVotes(address account_, uint256 timestamp_) external view returns (uint256);\n}\n" + }, + "src/utils/PositionFactory.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.29;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {SafeERC20} from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport {Ownable, Ownable2Step} from \"@openzeppelin/contracts/access/Ownable2Step.sol\";\nimport {IVeHemi} from \"../interfaces/IVeHemi.sol\";\n\ncontract PositionFactory is Ownable2Step {\n using SafeERC20 for IERC20;\n\n IVeHemi constant veHemi = IVeHemi(0x371d3718D5b7F75EAb050FAe6Da7DF3092031c89);\n IERC20 constant hemi = IERC20(0x99e3dE3817F6081B2568208337ef83295b7f591D);\n\n enum Status {\n NONE,\n PENDING,\n CREATED\n }\n\n mapping(bytes32 => Status) public created;\n\n event StatusUpdated(\n bytes32 indexed hash,\n address indexed user_,\n uint256 amount_,\n uint256 duration_,\n Status status\n );\n event PositionCreated(\n bytes32 indexed hash,\n address indexed user_,\n uint256 amount_,\n uint256 duration_,\n bool transferable_,\n bool forfeitable_\n );\n\n error PositionCreatedAlready(address user_, uint256 amount_, uint256 duration_);\n error InvalidArrays();\n\n constructor(address owner_) Ownable(owner_) {}\n\n function create(\n address user_,\n uint256 amount_,\n uint256 duration_,\n bool transferable_,\n bool forfeitable_\n ) external {\n bytes32 _hash = keccak256(abi.encodePacked(user_, amount_, duration_));\n\n Status _status = created[_hash];\n\n if (_status != Status.PENDING) revert PositionCreatedAlready(user_, amount_, duration_);\n\n hemi.safeTransferFrom(msg.sender, address(this), amount_);\n hemi.forceApprove(address(veHemi), amount_);\n veHemi.createLockFor(amount_, duration_, user_, transferable_, forfeitable_);\n\n created[_hash] = Status.CREATED;\n\n emit PositionCreated(_hash, user_, amount_, duration_, transferable_, forfeitable_);\n }\n\n function updateStatus(\n address[] calldata users_,\n uint256[] calldata amounts_,\n uint256[] calldata durations_,\n Status status_,\n bool revertIfCreated_\n ) external onlyOwner {\n uint256 _length = users_.length;\n\n if (_length != amounts_.length || _length != durations_.length) revert InvalidArrays();\n\n for (uint256 i; i < _length; ++i) {\n uint256 _amount = amounts_[i];\n uint256 _duration = durations_[i];\n address _user = users_[i];\n\n bytes32 _hash = keccak256(abi.encodePacked(_user, _amount, _duration));\n if (revertIfCreated_ && created[_hash] == Status.CREATED)\n revert PositionCreatedAlready(_user, _amount, _duration);\n created[_hash] = status_;\n\n emit StatusUpdated(_hash, _user, _amount, _duration, status_);\n }\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "evmVersion": "cancun", + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/docs/POSITIONS_CSV_FORMAT.md b/docs/POSITIONS_CSV_FORMAT.md new file mode 100644 index 0000000..7ae4358 --- /dev/null +++ b/docs/POSITIONS_CSV_FORMAT.md @@ -0,0 +1,111 @@ +# POSITIONS.csv Format + +## ๐Ÿ“‹ **Required Columns** + +The `POSITIONS.csv` file must have exactly these 5 columns in this order: + +| Column | Type | Description | Example | +|--------|------|-------------|---------| +| `wallet` | string | Ethereum wallet address | `0x1234567890123456789012345678901234567890` | +| `amount` | string | Amount in wei (not HEMI tokens) | `1000000000000000000000` (1000 HEMI) | +| `duration` | string | Lock duration in seconds | `31536000` (1 year) | +| `transferable` | string | Whether NFT is transferable | `true` or `false` | +| `forfeitable` | string | Whether position can be forfeited | `true` or `false` | + +## ๐Ÿ“ **CSV Format Example** + +```csv +wallet,amount,duration,transferable,forfeitable +0x1234567890123456789012345678901234567890,1000000000000000000000,31536000,true,false +0x2345678901234567890123456789012345678901,2000000000000000000000,63072000,true,false +0x3456789012345678901234567890123456789012,5000000000000000000000,94608000,false,true +``` + +## ๐Ÿ”ข **Amount Conversion** + +**Important**: The `amount` field must be in **wei**, not HEMI tokens. + +| HEMI Tokens | Wei Amount | +|-------------|------------| +| 1 HEMI | `1000000000000000000` | +| 100 HEMI | `100000000000000000000` | +| 1000 HEMI | `1000000000000000000000` | +| 10000 HEMI | `10000000000000000000000` | + +## โฐ **Duration Conversion** + +The `duration` field must be in **seconds**. + +| Duration | Seconds | +|----------|---------| +| 1 day | `86400` | +| 1 week | `604800` | +| 1 month | `2628000` | +| 6 months | `15768000` | +| 1 year | `31536000` | +| 2 years | `63072000` | +| 3 years | `94608000` | +| 4 years | `126144000` | + +## โœ… **Boolean Values** + +- `transferable`: `"true"` or `"false"` (as strings) +- `forfeitable`: `"true"` or `"false"` (as strings) + +## ๐Ÿšจ **Important Notes** + +1. **No Header Row**: The script expects the first row to be data, not headers +2. **Exact Column Order**: Columns must be in the exact order shown +3. **No Spaces**: Avoid spaces around values +4. **Valid Addresses**: All wallet addresses must be valid Ethereum addresses +5. **Wei Amounts**: Amounts must be in wei (multiply HEMI tokens by 10^18) + +## ๐Ÿงช **Testing with Local Environment** + +For local testing: + +```bash +# Set environment variable for local testing +export NODE_ENV=local + +# Run the script +npx ts-node scripts/positions-factory.ts +``` + +## ๐Ÿ“Š **Example Data** + +Here's a sample with different scenarios: + +```csv +wallet,amount,duration,transferable,forfeitable +0x1234567890123456789012345678901234567890,1000000000000000000000,31536000,true,false +0x2345678901234567890123456789012345678901,2000000000000000000000,63072000,true,false +0x3456789012345678901234567890123456789012,5000000000000000000000,94608000,false,true +0x4567890123456789012345678901234567890123,10000000000000000000000,126144000,true,false +0x5678901234567890123456789012345678901234,15000000000000000000000,157680000,true,false +``` + +## ๐Ÿ”ง **Script Usage** + +The script will: + +1. **Whitelist** all positions (set status to PENDING) +2. **Approve** HEMI tokens to the PositionFactory +3. **Create** each position individually +4. **Remove** the approval after completion + +## โš ๏ธ **Common Mistakes** + +1. **Using HEMI tokens instead of wei**: `1000` instead of `1000000000000000000000` +2. **Wrong column order**: Make sure columns are in exact order +3. **Invalid addresses**: Ensure all wallet addresses are valid +4. **String vs boolean**: Use `"true"`/`"false"` strings, not boolean values +5. **Duration in wrong units**: Use seconds, not days or years + +## ๐ŸŽฏ **Best Practices** + +1. **Test with small batches** first +2. **Validate addresses** before running +3. **Check amounts** are in wei +4. **Use consistent formatting** +5. **Keep backups** of your CSV files diff --git a/package.json b/package.json index 3eca7dd..b28557a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "test": "forge test", "test:gas": "forge test --gas-report", "verify": "hardhat etherscan-verify", - "verify:hemi": "hardhat etherscan-verify --network hemi --api-url https://explorer.hemi.xyz/api" + "verify:hemi": "hardhat etherscan-verify --network hemi --api-url https://explorer.hemi.xyz/api", + "positions:create": "npx ts-node scripts/positions-factory.ts" }, "devDependencies": { "@nomicfoundation/hardhat-foundry": "1.2.0", @@ -42,4 +43,4 @@ "solhint-plugin-prettier": "0.1.0", "solidity-docgen": "0.6.0-beta.36" } -} +} \ No newline at end of file diff --git a/scripts/positions-factory.ts b/scripts/positions-factory.ts index 52b9caa..1a4abdb 100644 --- a/scripts/positions-factory.ts +++ b/scripts/positions-factory.ts @@ -20,7 +20,7 @@ const FILE_PATH = "POSITIONS.csv"; const STATUS_BATCH_SIZE = 1000; // Each status update costs ~30k gas, so 1000 should fit in a block const RPC_URL = "https://rpc.hemi.network/rpc"; const HEMI_TOKEN_ADDRESS = "0x99e3dE3817F6081B2568208337ef83295b7f591D"; -const POSITION_FACTORY_ADDRESS = ""; // TODO +const POSITION_FACTORY_ADDRESS = "0xBFf8293Bafb943BE783d01f5C34d5382C2EeD90F"; const LOCAL_PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; // hardhat/anvil account[0] const LOCAL_RPC_URL = "http://localhost:8545"; @@ -80,9 +80,9 @@ const whitelist = async (rows: CsvRow[], factory: PositionFactory) => { forfeitables.push(forfeitable === "true"); } - const tx = await factory.updateStatus(users, amounts, durations, Status.PENDING); + const tx = await factory.updateStatus(users, amounts, durations, Status.PENDING, true); console.log(`Batch ${i++} transaction hash: ${tx.hash}`); - await tx.wait(2); + await tx.wait(1); } }; @@ -92,7 +92,7 @@ const create = async (rows: CsvRow[], factory: PositionFactory, wallet: Wallet) const hemi = new ethers.Contract(HEMI_TOKEN_ADDRESS, IERC20__factory.abi, wallet); const approveMaxTx = await hemi.approve(factory.target, ethers.MaxUint256); console.log("Hemi infinity approval transaction hash:", approveMaxTx.hash); - await approveMaxTx.wait(2); + await approveMaxTx.wait(1); let i = 1; for (const { wallet, amount, duration, transferable, forfeitable } of rows) { @@ -124,12 +124,12 @@ const create = async (rows: CsvRow[], factory: PositionFactory, wallet: Wallet) forfeitable === "true" ); console.log("Transaction hash:", tx.hash); - await tx.wait(2); + await tx.wait(1); } const approveZeroTx = await hemi.approve(factory.target, 0); console.log("\nHemi remove approval transaction hash:", approveZeroTx.hash); - await approveZeroTx.wait(2); + await approveZeroTx.wait(1); }; /** @@ -143,16 +143,21 @@ const create = async (rows: CsvRow[], factory: PositionFactory, wallet: Wallet) * - Run `npx hardhat compile` when changing the smart contract */ const main = async () => { + console.log("๐Ÿš€ Starting positions-factory script..."); + console.log(`NODE_ENV: ${NODE_ENV}`); + console.log(`FILE_PATH: ${FILE_PATH}`); + let provider: JsonRpcProvider; let wallet: Wallet; let factory: PositionFactory; if (NODE_ENV == "local") { + console.log("๐Ÿ”ง Running in LOCAL mode"); provider = new ethers.JsonRpcProvider(LOCAL_RPC_URL); wallet = new ethers.Wallet(LOCAL_PRIVATE_KEY, provider); factory = await new PositionFactory__factory(wallet).deploy(wallet); const deploymentTx = factory.deploymentTransaction()!; - await deploymentTx.wait(2); + await deploymentTx.wait(1); // Wait for 1 confirmation instead of 2 // deal 1M HEMI to our wallet const slot = 0; // HEMI balance slot @@ -160,7 +165,16 @@ const main = async () => { const index = stripZerosLeft(hexlify(solidityPackedKeccak256(["uint256", "uint256"], [wallet.address, slot]))); const value = hexlify(toBeHex(balance, 32)); await provider.send("hardhat_setStorageAt", [HEMI_TOKEN_ADDRESS, index, value]); + console.log("โœ… Local setup complete - deployed factory and funded wallet"); } else { + console.log("๐ŸŒ Running in PRODUCTION mode"); + console.log(`RPC_URL: ${RPC_URL}`); + console.log(`POSITION_FACTORY_ADDRESS: ${POSITION_FACTORY_ADDRESS}`); + + if (!PRIVATE_KEY) { + throw new Error("PRIVATE_KEY environment variable is required for production mode"); + } + provider = new ethers.JsonRpcProvider(RPC_URL); wallet = new ethers.Wallet(PRIVATE_KEY!, provider); factory = new ethers.Contract( @@ -168,12 +182,17 @@ const main = async () => { PositionFactory__factory.abi, wallet ) as PositionFactory & Contract; + console.log("โœ… Production setup complete"); } + console.log(`๐Ÿ“– Reading CSV file: ${FILE_PATH}`); const rows = (await readCsv(FILE_PATH)) as CsvRow[]; + console.log(`๐Ÿ“Š Found ${rows.length} positions to process`); await whitelist(rows, factory); await create(rows, factory, wallet); + + console.log("๐ŸŽ‰ Script completed successfully!"); }; main().catch(console.error); diff --git a/src/utils/PositionFactory.sol b/src/utils/PositionFactory.sol index 6c1ebae..14239ab 100644 --- a/src/utils/PositionFactory.sol +++ b/src/utils/PositionFactory.sol @@ -36,7 +36,7 @@ contract PositionFactory is Ownable2Step { bool forfeitable_ ); - error PositionCreatedAlready(); + error PositionCreatedAlready(address user_, uint256 amount_, uint256 duration_); error InvalidArrays(); constructor(address owner_) Ownable(owner_) {} @@ -52,7 +52,7 @@ contract PositionFactory is Ownable2Step { Status _status = created[_hash]; - if (_status != Status.PENDING) revert PositionCreatedAlready(); + if (_status != Status.PENDING) revert PositionCreatedAlready(user_, amount_, duration_); hemi.safeTransferFrom(msg.sender, address(this), amount_); hemi.forceApprove(address(veHemi), amount_); @@ -67,7 +67,8 @@ contract PositionFactory is Ownable2Step { address[] calldata users_, uint256[] calldata amounts_, uint256[] calldata durations_, - Status status_ + Status status_, + bool revertIfCreated_ ) external onlyOwner { uint256 _length = users_.length; @@ -79,7 +80,8 @@ contract PositionFactory is Ownable2Step { address _user = users_[i]; bytes32 _hash = keccak256(abi.encodePacked(_user, _amount, _duration)); - + if (revertIfCreated_ && created[_hash] == Status.CREATED) + revert PositionCreatedAlready(_user, _amount, _duration); created[_hash] = status_; emit StatusUpdated(_hash, _user, _amount, _duration, status_);