diff --git a/packages/storage-evm/.openzeppelin/base.json b/packages/storage-evm/.openzeppelin/base.json index f4281b478..3b9ee024e 100644 --- a/packages/storage-evm/.openzeppelin/base.json +++ b/packages/storage-evm/.openzeppelin/base.json @@ -103,6 +103,11 @@ { "address": "0x299f4D2327933c1f363301dbd2a28379ccD5539b", "kind": "uups" + }, + { + "address": "0xf44ABfc7606cC97794A8E036823d1edd6d34786c", + "txHash": "0x7d3705bcaf64094c9bb0904a5f723ed7e0c234e3b367bdbadd59bae47d116834", + "kind": "uups" } ], "impls": { @@ -7454,6 +7459,377 @@ } ] } + }, + "allAddresses": [ + "0x0A6bB8d5FE1042146a63f3C36261E97840b6eC00" + ] + }, + "9008243dd9d49862e7f3150b03f84507ae2d28f85dbcfbfad9b8d312afaecba1": { + "address": "0x0A6bB8d5FE1042146a63f3C36261E97840b6eC00", + "layout": { + "solcVersion": "0.8.28", + "storage": [ + { + "label": "spacesContract", + "offset": 0, + "slot": "0", + "type": "t_address", + "contract": "DecayingTokenFactoryStorage", + "src": "contracts/storage/DecayingTokenFactoryStorage.sol:7" + }, + { + "label": "decayVotingPowerContract", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "DecayingTokenFactoryStorage", + "src": "contracts/storage/DecayingTokenFactoryStorage.sol:8" + }, + { + "label": "isTokenDeployedByFactory", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_address,t_bool)", + "contract": "DecayingTokenFactoryStorage", + "src": "contracts/storage/DecayingTokenFactoryStorage.sol:9" + }, + { + "label": "spaceTokens", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_uint256,t_address)", + "contract": "DecayingTokenFactoryStorage", + "src": "contracts/storage/DecayingTokenFactoryStorage.sol:12" + }, + { + "label": "proposalsContract", + "offset": 0, + "slot": "4", + "type": "t_address", + "contract": "DecayingTokenFactory", + "src": "contracts/DecayingTokenFactory.sol:55" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_struct(InitializableStorage)73_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(OwnableStorage)13_storage": { + "label": "struct OwnableUpgradeable.OwnableStorage", + "members": [ + { + "label": "_owner", + "type": "t_address", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_address)": { + "label": "mapping(uint256 => address)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Ownable": [ + { + "contract": "OwnableUpgradeable", + "label": "_owner", + "type": "t_address", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:24", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } + }, + "cfe26594eb69db7bce6e71e9c28d59fed0c67a650661d46f7550708cbfa9e03a": { + "address": "0xc6F74D6555c9f77097F01590f5C404b5150603de", + "txHash": "0xea42071940a5908e4fc6af16feaf4ec94b3ede30316624eedeacc1c1372dabe6", + "layout": { + "solcVersion": "0.8.28", + "storage": [ + { + "label": "spacesContract", + "offset": 0, + "slot": "0", + "type": "t_address", + "contract": "OwnershipTokenFactoryStorage", + "src": "contracts/storage/OwnershipTokenFactoryStorage.sol:7" + }, + { + "label": "votingPowerContract", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "OwnershipTokenFactoryStorage", + "src": "contracts/storage/OwnershipTokenFactoryStorage.sol:8" + }, + { + "label": "isTokenDeployedByFactory", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_address,t_bool)", + "contract": "OwnershipTokenFactoryStorage", + "src": "contracts/storage/OwnershipTokenFactoryStorage.sol:9" + }, + { + "label": "spaceTokens", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_uint256,t_address)", + "contract": "OwnershipTokenFactoryStorage", + "src": "contracts/storage/OwnershipTokenFactoryStorage.sol:12" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_struct(InitializableStorage)73_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(OwnableStorage)13_storage": { + "label": "struct OwnableUpgradeable.OwnableStorage", + "members": [ + { + "label": "_owner", + "type": "t_address", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_address)": { + "label": "mapping(uint256 => address)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Ownable": [ + { + "contract": "OwnableUpgradeable", + "label": "_owner", + "type": "t_address", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:24", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } + } + }, + "0eba1de55b3348bd9c6f03ba6bb5b5edb267d0bad80fed7f2a665251a1f4480f": { + "address": "0xC4c09167833d389C9e28CEAD146cB7e8B58940D6", + "txHash": "0xa7bb79abe3ed0fc37afbeb310b2904c2c63786c86d5a994b1031881a54429557", + "layout": { + "solcVersion": "0.8.28", + "storage": [ + { + "label": "spaceTokens", + "offset": 0, + "slot": "0", + "type": "t_mapping(t_uint256,t_address)", + "contract": "TokenVotingPowerStorage", + "src": "contracts/storage/TokenVotingPowerStorage.sol:14" + }, + { + "label": "ownershipTokenFactory", + "offset": 0, + "slot": "1", + "type": "t_address", + "contract": "OwnershipTokenVotingPowerStorage", + "src": "contracts/storage/OwnershipTokenVotingPowerStorage.sol:13" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_struct(InitializableStorage)73_storage": { + "label": "struct Initializable.InitializableStorage", + "members": [ + { + "label": "_initialized", + "type": "t_uint64", + "offset": 0, + "slot": "0" + }, + { + "label": "_initializing", + "type": "t_bool", + "offset": 8, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_struct(OwnableStorage)13_storage": { + "label": "struct OwnableUpgradeable.OwnableStorage", + "members": [ + { + "label": "_owner", + "type": "t_address", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "32" + }, + "t_uint64": { + "label": "uint64", + "numberOfBytes": "8" + }, + "t_mapping(t_uint256,t_address)": { + "label": "mapping(uint256 => address)", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + } + }, + "namespaces": { + "erc7201:openzeppelin.storage.Ownable": [ + { + "contract": "OwnableUpgradeable", + "label": "_owner", + "type": "t_address", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:24", + "offset": 0, + "slot": "0" + } + ], + "erc7201:openzeppelin.storage.Initializable": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_uint64", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:69", + "offset": 0, + "slot": "0" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:73", + "offset": 8, + "slot": "0" + } + ] + } } } } diff --git a/packages/storage-evm/contracts/addresses.txt b/packages/storage-evm/contracts/addresses.txt index 7204bf4e1..60864a1ae 100644 --- a/packages/storage-evm/contracts/addresses.txt +++ b/packages/storage-evm/contracts/addresses.txt @@ -55,4 +55,8 @@ Deploying with admin address: 0x2687fe290b54d824c136Ceff2d5bD362Bc62019a Deploying Agreements... Agreements deployed to: 0x83B5d4F555A68126bB302685e67767Bb7a2985F0 -OwnershipTokenFactory proxy deployed to: 0xA1eDf096B72226ae2f7BDEb12E9c9C82152BccB6 \ No newline at end of file +OwnershipTokenFactory proxy deployed to: 0xA1eDf096B72226ae2f7BDEb12E9c9C82152BccB6 + +Deploying with admin address: 0x2687fe290b54d824c136Ceff2d5bD362Bc62019a +Deploying OwnershipTokenVotingPower... +OwnershipTokenVotingPowerImplementation proxy deployed to: 0xf44ABfc7606cC97794A8E036823d1edd6d34786c \ No newline at end of file diff --git a/packages/storage-evm/scripts/README.md b/packages/storage-evm/scripts/README.md index 5b8d64d4e..7683e5690 100644 --- a/packages/storage-evm/scripts/README.md +++ b/packages/storage-evm/scripts/README.md @@ -144,3 +144,11 @@ npx nx run storage-evm:script ./scripts/token-voting-power.upgrade.ts --network ```bash npx nx run storage-evm:script ./scripts/votedecay-voting-power.upgrade.ts --network base-mainnet ``` + +### Upgrade Decaying Token Factory + +```bash +npx nx run storage-evm:script ./scripts/decaying-token-factory.upgrade.ts --network base-mainnet + +npx nx run storage-evm:script ./scripts/ownership-token-voting-power-proxy.deploy.ts --network base-mainnet +``` diff --git a/packages/storage-evm/scripts/base-mainnet-contracts-scripts/configure-ownership-token-contracts.ts b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/configure-ownership-token-contracts.ts new file mode 100644 index 000000000..f155c3f3e --- /dev/null +++ b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/configure-ownership-token-contracts.ts @@ -0,0 +1,245 @@ +import dotenv from 'dotenv'; +import { ethers } from 'ethers'; +import fs from 'fs'; +import path from 'path'; + +dotenv.config(); + +// Add interface definitions +interface Log { + topics: string[]; + [key: string]: any; +} + +interface TransactionReceipt { + logs: Log[]; + [key: string]: any; +} + +interface ContractTransactionWithWait extends ethers.ContractTransaction { + wait(): Promise; +} + +interface OwnershipTokenFactoryInterface { + setVotingPowerContract: ( + votingPowerContract: string, + ) => Promise; + owner(): Promise; +} + +interface OwnershipTokenVotingPowerInterface { + setOwnershipTokenFactory: ( + tokenFactory: string, + ) => Promise; + owner(): Promise; +} + +// Function to parse addresses from addresses.txt +function parseAddressesFile(): Record { + const addressesPath = path.resolve( + __dirname, + '../../contracts/addresses.txt', + ); + const fileContent = fs.readFileSync(addressesPath, 'utf8'); + + const addresses: Record = {}; + + // Extract contract addresses using regex + const patterns = { + OwnershipTokenFactory: + /OwnershipTokenFactory proxy deployed to: (0x[a-fA-F0-9]{40})/, + OwnershipTokenVotingPowerImplementation: + /OwnershipTokenVotingPowerImplementation proxy deployed to: (0x[a-fA-F0-9]{40})/, + }; + + for (const [key, pattern] of Object.entries(patterns)) { + const match = fileContent.match(pattern); + if (match && match[1]) { + addresses[key] = match[1]; + } + } + + return addresses; +} + +const ownershipTokenFactoryAbi = [ + { + inputs: [ + { + internalType: 'address', + name: '_votingPowerContract', + type: 'address', + }, + ], + name: 'setVotingPowerContract', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]; + +const ownershipTokenVotingPowerAbi = [ + { + inputs: [ + { + internalType: 'address', + name: '_tokenFactory', + type: 'address', + }, + ], + name: 'setOwnershipTokenFactory', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]; + +async function main(): Promise { + // Parse addresses from file + const addresses = parseAddressesFile(); + + // Check if OwnershipTokenFactory address is available + if (!addresses['OwnershipTokenFactory']) { + throw new Error('OwnershipTokenFactory address not found in addresses.txt'); + } + + // Check if OwnershipTokenVotingPowerImplementation address is available + if (!addresses['OwnershipTokenVotingPowerImplementation']) { + console.warn( + 'Warning: OwnershipTokenVotingPowerImplementation address not found in addresses.txt', + ); + console.warn( + 'Please add the deployed OwnershipTokenVotingPowerImplementation proxy address to addresses.txt', + ); + console.warn( + 'Format: OwnershipTokenVotingPowerImplementation proxy deployed to:
', + ); + throw new Error( + 'OwnershipTokenVotingPowerImplementation address not found', + ); + } + + // Connect to the network + const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); + + // Create a wallet instance + const wallet = new ethers.Wallet(process.env.PRIVATE_KEY || '', provider); + + console.log('Configuring ownership token contracts...'); + console.log('Wallet address:', wallet.address); + console.log( + 'OwnershipTokenFactory address:', + addresses['OwnershipTokenFactory'], + ); + console.log( + 'OwnershipTokenVotingPowerImplementation address:', + addresses['OwnershipTokenVotingPowerImplementation'], + ); + + // Get contract instances + const ownershipTokenFactory = new ethers.Contract( + addresses['OwnershipTokenFactory'], + ownershipTokenFactoryAbi, + wallet, + ) as ethers.Contract & OwnershipTokenFactoryInterface; + + const ownershipTokenVotingPower = new ethers.Contract( + addresses['OwnershipTokenVotingPowerImplementation'], + ownershipTokenVotingPowerAbi, + wallet, + ) as ethers.Contract & OwnershipTokenVotingPowerInterface; + + // Check ownership of both contracts + console.log('\nChecking contract ownership...'); + + const factoryOwner = await ownershipTokenFactory.owner(); + if (factoryOwner.toLowerCase() !== wallet.address.toLowerCase()) { + console.error( + `Your wallet (${wallet.address}) is not the owner of the OwnershipTokenFactory contract.`, + ); + console.error(`The owner is: ${factoryOwner}`); + throw new Error( + 'Permission denied: only the contract owner can call setVotingPowerContract', + ); + } + console.log('โœ“ You are the owner of OwnershipTokenFactory'); + + const votingPowerOwner = await ownershipTokenVotingPower.owner(); + if (votingPowerOwner.toLowerCase() !== wallet.address.toLowerCase()) { + console.error( + `Your wallet (${wallet.address}) is not the owner of the OwnershipTokenVotingPowerImplementation contract.`, + ); + console.error(`The owner is: ${votingPowerOwner}`); + throw new Error( + 'Permission denied: only the contract owner can call setOwnershipTokenFactory', + ); + } + console.log('โœ“ You are the owner of OwnershipTokenVotingPowerImplementation'); + + try { + // Step 1: Set voting power contract in OwnershipTokenFactory + console.log( + '\n1. Setting voting power contract in OwnershipTokenFactory...', + ); + const tx1 = await ownershipTokenFactory.setVotingPowerContract( + addresses['OwnershipTokenVotingPowerImplementation'], + ); + console.log('Transaction sent, waiting for confirmation...'); + await tx1.wait(); + console.log( + 'โœ“ Voting power contract set successfully in OwnershipTokenFactory!', + ); + + // Step 2: Set ownership token factory in OwnershipTokenVotingPowerImplementation + console.log( + '\n2. Setting ownership token factory in OwnershipTokenVotingPowerImplementation...', + ); + const tx2 = await ownershipTokenVotingPower.setOwnershipTokenFactory( + addresses['OwnershipTokenFactory'], + ); + console.log('Transaction sent, waiting for confirmation...'); + await tx2.wait(); + console.log( + 'โœ“ Ownership token factory set successfully in OwnershipTokenVotingPowerImplementation!', + ); + + console.log('\n๐ŸŽ‰ All contracts configured successfully!'); + console.log('The contracts are now properly linked and ready to use.'); + } catch (error: any) { + console.error('Error configuring contracts:', error.message); + throw error; + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/packages/storage-evm/scripts/base-mainnet-contracts-scripts/debug-decaying-factory.ts b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/debug-decaying-factory.ts new file mode 100644 index 000000000..7e4ef6df6 --- /dev/null +++ b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/debug-decaying-factory.ts @@ -0,0 +1,51 @@ +import dotenv from 'dotenv'; +import { ethers } from 'ethers'; + +dotenv.config(); + +const DECAYING_TOKEN_FACTORY = '0x299f4D2327933c1f363301dbd2a28379ccD5539b'; + +async function main(): Promise { + if (!process.env.RPC_URL) { + throw new Error('Missing required environment variable: RPC_URL'); + } + + const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); + + // Try to get the contract code to see if it's deployed + const code = await provider.getCode(DECAYING_TOKEN_FACTORY); + console.log('Contract code length:', code.length); + + if (code === '0x') { + console.log('No contract deployed at this address!'); + return; + } + + // Try calling with a broader ABI to see what happens + const testAbi = [ + 'function getSpaceToken(uint256) view returns (address)', + 'function spaceTokens(uint256) view returns (address)', + 'function tokens(uint256) view returns (address)', + ]; + + const contract = new ethers.Contract( + DECAYING_TOKEN_FACTORY, + testAbi, + provider, + ); + + // Test different possible function names + const testFunctions = ['getSpaceToken', 'spaceTokens', 'tokens']; + + for (const funcName of testFunctions) { + try { + console.log(`\nTrying function: ${funcName}(94)`); + const result = await contract[funcName](94); + console.log(`Result: ${result}`); + } catch (error: any) { + console.log(`Failed: ${error.message.split('(')[0]}`); + } + } +} + +main().catch(console.error); diff --git a/packages/storage-evm/scripts/base-mainnet-contracts-scripts/list-decaying-space-tokens.ts b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/list-decaying-space-tokens.ts new file mode 100644 index 000000000..31a7af258 --- /dev/null +++ b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/list-decaying-space-tokens.ts @@ -0,0 +1,313 @@ +import dotenv from 'dotenv'; +import { ethers } from 'ethers'; + +dotenv.config(); + +// Base Mainnet contract addresses +const CONTRACTS = { + DECAYING_TOKEN_FACTORY: '0x95A33EC94de2189893884DaD63eAa19f7390144a', + DAO_SPACE_FACTORY: '0xc8B8454D2F9192FeCAbc2C6F5d88F6434A2a9cd9', +}; + +interface DecayingTokenFactoryInterface { + getSpaceToken: (spaceId: number) => Promise; +} + +interface DAOSpaceFactoryInterface { + spaceCounter: () => Promise; +} + +const decayingTokenFactoryAbi = [ + { + inputs: [ + { + internalType: 'uint256', + name: 'spaceId', + type: 'uint256', + }, + ], + name: 'getSpaceToken', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]; + +const daoSpaceFactoryAbi = [ + { + inputs: [], + name: 'spaceCounter', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]; + +async function getDecayingTokenForSpace( + tokenFactory: ethers.Contract & DecayingTokenFactoryInterface, + spaceId: number, +): Promise { + try { + const tokenAddress = await tokenFactory.getSpaceToken(spaceId); + + if (tokenAddress === ethers.ZeroAddress) { + console.log(`Space ${spaceId}: No decaying token deployed`); + } else { + console.log( + `Space ${spaceId}: Decaying token deployed at ${tokenAddress}`, + ); + } + } catch (error: any) { + // If the call reverts, it likely means no token is deployed for this space + // This is different behavior from RegularTokenFactory which returns zero address + if (error.code === 'CALL_EXCEPTION') { + console.log(`Space ${spaceId}: No decaying token deployed`); + } else { + console.error( + `Error fetching decaying token for space ${spaceId}:`, + error.message, + ); + } + } +} + +async function checkContractInterface( + provider: ethers.JsonRpcProvider, +): Promise { + console.log('Checking DecayingTokenFactory contract interface...'); + + // First check if contract exists + const code = await provider.getCode(CONTRACTS.DECAYING_TOKEN_FACTORY); + if (code === '0x') { + console.log('โŒ No contract deployed at this address!'); + return false; + } + console.log('โœ… Contract is deployed'); + + // Try different possible function signatures + const testAbi = [ + 'function getSpaceToken(uint256) view returns (address)', + 'function spaceTokens(uint256) view returns (address)', + 'function tokens(uint256) view returns (address)', + 'function getToken(uint256) view returns (address)', + ]; + + const contract = new ethers.Contract( + CONTRACTS.DECAYING_TOKEN_FACTORY, + testAbi, + provider, + ); + + const testFunctions = ['getSpaceToken', 'spaceTokens', 'tokens', 'getToken']; + let foundFunction = false; + + for (const funcName of testFunctions) { + try { + console.log(`Testing function: ${funcName}...`); + // Try with a space that definitely exists (space 1) + const result = await contract[funcName](1); + console.log( + `โœ… Function ${funcName} exists! Result for space 1: ${result}`, + ); + foundFunction = true; + + if (funcName !== 'getSpaceToken') { + console.log( + `โš ๏ธ Note: Function name is ${funcName}, not getSpaceToken`, + ); + } + + break; + } catch (error: any) { + if ( + error.code === 'CALL_EXCEPTION' && + error.message.includes('missing revert data') + ) { + console.log( + `โœ… Function ${funcName} exists but reverted (likely no token for space 1)`, + ); + foundFunction = true; + + if (funcName !== 'getSpaceToken') { + console.log( + `โš ๏ธ Note: Function name is ${funcName}, not getSpaceToken`, + ); + } + + break; + } else { + console.log( + `โŒ Function ${funcName} does not exist or failed: ${ + error.message.split('(')[0] + }`, + ); + } + } + } + + if (!foundFunction) { + console.log('โŒ No compatible function found on the contract'); + } + + return foundFunction; +} + +async function main(): Promise { + // Validate required environment variables + if (!process.env.RPC_URL) { + throw new Error('Missing required environment variable: RPC_URL'); + } + + // Connect to the network + const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); + + // Check if the contract has the expected interface + const hasValidInterface = await checkContractInterface(provider); + if (!hasValidInterface) { + throw new Error( + 'DecayingTokenFactory contract does not have the expected interface', + ); + } + console.log(''); // Add blank line for readability + + // Get the DecayingTokenFactory contract instance + const decayingTokenFactory = new ethers.Contract( + CONTRACTS.DECAYING_TOKEN_FACTORY, + decayingTokenFactoryAbi, + provider, + ) as ethers.Contract & DecayingTokenFactoryInterface; + + // Get the DAOSpaceFactory contract instance to get total space count + const daoSpaceFactory = new ethers.Contract( + CONTRACTS.DAO_SPACE_FACTORY, + daoSpaceFactoryAbi, + provider, + ) as ethers.Contract & DAOSpaceFactoryInterface; + + try { + // Check if a specific space ID is provided as command line argument + const args = process.argv.slice(2); + + if (args.length > 0) { + // Query specific space ID(s) + for (const arg of args) { + const spaceId = parseInt(arg); + if (isNaN(spaceId)) { + console.error(`Invalid space ID: ${arg}`); + continue; + } + console.log(`\n=== Querying Decaying Token for Space ${spaceId} ===`); + await getDecayingTokenForSpace(decayingTokenFactory, spaceId); + } + } else { + // Query all spaces + const spaceCounter = await daoSpaceFactory.spaceCounter(); + console.log(`Total number of spaces: ${spaceCounter}`); + console.log('\n=== Decaying Token Addresses for All Spaces ===\n'); + + const spacesWithTokens: Array<{ spaceId: number; tokenAddress: string }> = + []; + const spacesWithoutTokens: number[] = []; + + // Iterate through all spaces + for (let spaceId = 1; spaceId <= Number(spaceCounter); spaceId++) { + try { + const tokenAddress = await decayingTokenFactory.getSpaceToken( + spaceId, + ); + + if (tokenAddress === ethers.ZeroAddress) { + spacesWithoutTokens.push(spaceId); + } else { + spacesWithTokens.push({ spaceId, tokenAddress }); + console.log(`Space ${spaceId}: ${tokenAddress}`); + } + } catch (error: any) { + // If the call reverts, treat it as no token deployed + if (error.code === 'CALL_EXCEPTION') { + spacesWithoutTokens.push(spaceId); + } else { + console.error( + `Error fetching decaying token for space ${spaceId}:`, + error.message, + ); + spacesWithoutTokens.push(spaceId); + } + } + } + + // Summary + console.log('\n=== Summary ==='); + console.log(`Total spaces: ${spaceCounter}`); + console.log(`Spaces with decaying tokens: ${spacesWithTokens.length}`); + console.log( + `Spaces without decaying tokens: ${spacesWithoutTokens.length}`, + ); + + if (spacesWithoutTokens.length > 0) { + console.log( + `\nSpaces without decaying tokens: ${spacesWithoutTokens.join(', ')}`, + ); + } + } + } catch (error: any) { + console.error('Error:', error.message); + throw error; + } +} + +// Add this test function after the main function +async function testDecayingTokens(): Promise { + if (!process.env.RPC_URL) { + throw new Error('Missing required environment variable: RPC_URL'); + } + + const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); + const decayingTokenFactory = new ethers.Contract( + CONTRACTS.DECAYING_TOKEN_FACTORY, + decayingTokenFactoryAbi, + provider, + ) as ethers.Contract & DecayingTokenFactoryInterface; + + console.log('Testing first 10 spaces for decaying tokens:'); + + for (let spaceId = 1; spaceId <= 10; spaceId++) { + try { + const tokenAddress = await decayingTokenFactory.getSpaceToken(spaceId); + if (tokenAddress !== ethers.ZeroAddress) { + console.log( + `Space ${spaceId}: FOUND decaying token at ${tokenAddress}`, + ); + } else { + console.log(`Space ${spaceId}: Zero address returned`); + } + } catch (error: any) { + console.log(`Space ${spaceId}: Call reverted (likely no token)`); + } + } +} + +main() + .then(() => { + console.log('\nScript completed successfully'); + process.exit(0); + }) + .catch((error) => { + console.error(error); + process.exit(1); + }); + +// Add this to the bottom of the file to test +// testDecayingTokens().catch(console.error); diff --git a/packages/storage-evm/scripts/base-mainnet-contracts-scripts/list-ownership-space-tokens.ts b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/list-ownership-space-tokens.ts new file mode 100644 index 000000000..373bd8d40 --- /dev/null +++ b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/list-ownership-space-tokens.ts @@ -0,0 +1,267 @@ +import dotenv from 'dotenv'; +import { ethers } from 'ethers'; + +dotenv.config(); + +// Base Mainnet contract addresses +const CONTRACTS = { + OWNERSHIP_TOKEN_FACTORY: '0xA1eDf096B72226ae2f7BDEb12E9c9C82152BccB6', + DAO_SPACE_FACTORY: '0xc8B8454D2F9192FeCAbc2C6F5d88F6434A2a9cd9', +}; + +interface OwnershipTokenFactoryInterface { + getSpaceToken: (spaceId: number) => Promise; +} + +interface DAOSpaceFactoryInterface { + spaceCounter: () => Promise; +} + +const ownershipTokenFactoryAbi = [ + { + inputs: [ + { + internalType: 'uint256', + name: 'spaceId', + type: 'uint256', + }, + ], + name: 'getSpaceToken', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]; + +const daoSpaceFactoryAbi = [ + { + inputs: [], + name: 'spaceCounter', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]; + +async function getOwnershipTokenForSpace( + tokenFactory: ethers.Contract & OwnershipTokenFactoryInterface, + spaceId: number, +): Promise { + try { + const tokenAddress = await tokenFactory.getSpaceToken(spaceId); + + if (tokenAddress === ethers.ZeroAddress) { + console.log(`Space ${spaceId}: No ownership token deployed`); + } else { + console.log( + `Space ${spaceId}: Ownership token deployed at ${tokenAddress}`, + ); + } + } catch (error: any) { + console.error( + `Error fetching ownership token for space ${spaceId}:`, + error.message, + ); + } +} + +async function checkContractInterface( + provider: ethers.JsonRpcProvider, +): Promise { + console.log('Checking OwnershipTokenFactory contract interface...'); + + // First check if contract exists + const code = await provider.getCode(CONTRACTS.OWNERSHIP_TOKEN_FACTORY); + if (code === '0x') { + console.log('โŒ No contract deployed at this address!'); + return false; + } + console.log('โœ… Contract is deployed'); + + // Try different possible function signatures + const testAbi = [ + 'function getSpaceToken(uint256) view returns (address)', + 'function spaceTokens(uint256) view returns (address)', + 'function tokens(uint256) view returns (address)', + 'function getToken(uint256) view returns (address)', + ]; + + const contract = new ethers.Contract( + CONTRACTS.OWNERSHIP_TOKEN_FACTORY, + testAbi, + provider, + ); + + const testFunctions = ['getSpaceToken', 'spaceTokens', 'tokens', 'getToken']; + let foundFunction = false; + + for (const funcName of testFunctions) { + try { + console.log(`Testing function: ${funcName}...`); + // Try with a space that definitely exists (space 1) + const result = await contract[funcName](1); + console.log( + `โœ… Function ${funcName} exists! Result for space 1: ${result}`, + ); + foundFunction = true; + + if (funcName !== 'getSpaceToken') { + console.log( + `โš ๏ธ Note: Function name is ${funcName}, not getSpaceToken`, + ); + } + + break; + } catch (error: any) { + if (error.code === 'CALL_EXCEPTION') { + console.log( + `โœ… Function ${funcName} exists but reverted (likely no token for space 1)`, + ); + foundFunction = true; + + if (funcName !== 'getSpaceToken') { + console.log( + `โš ๏ธ Note: Function name is ${funcName}, not getSpaceToken`, + ); + } + + break; + } else { + console.log( + `โŒ Function ${funcName} does not exist or failed: ${ + error.message.split('(')[0] + }`, + ); + } + } + } + + if (!foundFunction) { + console.log('โŒ No compatible function found on the contract'); + } + + return foundFunction; +} + +async function main(): Promise { + // Validate required environment variables + if (!process.env.RPC_URL) { + throw new Error('Missing required environment variable: RPC_URL'); + } + + // Connect to the network + const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); + + // Check if the contract has the expected interface + const hasValidInterface = await checkContractInterface(provider); + if (!hasValidInterface) { + throw new Error( + 'OwnershipTokenFactory contract does not have the expected interface', + ); + } + console.log(''); // Add blank line for readability + + // Get the OwnershipTokenFactory contract instance + const ownershipTokenFactory = new ethers.Contract( + CONTRACTS.OWNERSHIP_TOKEN_FACTORY, + ownershipTokenFactoryAbi, + provider, + ) as ethers.Contract & OwnershipTokenFactoryInterface; + + // Get the DAOSpaceFactory contract instance to get total space count + const daoSpaceFactory = new ethers.Contract( + CONTRACTS.DAO_SPACE_FACTORY, + daoSpaceFactoryAbi, + provider, + ) as ethers.Contract & DAOSpaceFactoryInterface; + + try { + // Check if a specific space ID is provided as command line argument + const args = process.argv.slice(2); + + if (args.length > 0) { + // Query specific space ID(s) + for (const arg of args) { + const spaceId = parseInt(arg); + if (isNaN(spaceId)) { + console.error(`Invalid space ID: ${arg}`); + continue; + } + console.log(`\n=== Querying Ownership Token for Space ${spaceId} ===`); + await getOwnershipTokenForSpace(ownershipTokenFactory, spaceId); + } + } else { + // Query all spaces + const spaceCounter = await daoSpaceFactory.spaceCounter(); + console.log(`Total number of spaces: ${spaceCounter}`); + console.log('\n=== Ownership Token Addresses for All Spaces ===\n'); + + const spacesWithTokens: Array<{ spaceId: number; tokenAddress: string }> = + []; + const spacesWithoutTokens: number[] = []; + + // Iterate through all spaces + for (let spaceId = 1; spaceId <= Number(spaceCounter); spaceId++) { + try { + const tokenAddress = await ownershipTokenFactory.getSpaceToken( + spaceId, + ); + + if (tokenAddress === ethers.ZeroAddress) { + spacesWithoutTokens.push(spaceId); + } else { + spacesWithTokens.push({ spaceId, tokenAddress }); + console.log(`Space ${spaceId}: ${tokenAddress}`); + } + } catch (error: any) { + console.error( + `Error fetching ownership token for space ${spaceId}:`, + error.message, + ); + spacesWithoutTokens.push(spaceId); + } + } + + // Summary + console.log('\n=== Summary ==='); + console.log(`Total spaces: ${spaceCounter}`); + console.log(`Spaces with ownership tokens: ${spacesWithTokens.length}`); + console.log( + `Spaces without ownership tokens: ${spacesWithoutTokens.length}`, + ); + + if (spacesWithoutTokens.length > 0) { + console.log( + `\nSpaces without ownership tokens: ${spacesWithoutTokens.join( + ', ', + )}`, + ); + } + } + } catch (error: any) { + console.error('Error:', error.message); + throw error; + } +} + +main() + .then(() => { + console.log('\nScript completed successfully'); + process.exit(0); + }) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/packages/storage-evm/scripts/base-mainnet-contracts-scripts/set-decay-token-factory-in-votedecaytokenvotingpower.ts b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/set-decay-token-factory-in-votedecaytokenvotingpower.ts new file mode 100644 index 000000000..5501e9d32 --- /dev/null +++ b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/set-decay-token-factory-in-votedecaytokenvotingpower.ts @@ -0,0 +1,203 @@ +import dotenv from 'dotenv'; +import { ethers } from 'ethers'; +import fs from 'fs'; +import path from 'path'; + +dotenv.config(); + +// Add interface definitions +interface Log { + topics: string[]; + [key: string]: any; +} + +interface TransactionReceipt { + logs: Log[]; + [key: string]: any; +} + +interface ContractTransactionWithWait extends ethers.ContractTransaction { + wait(): Promise; +} + +interface VoteDecayTokenVotingPowerInterface { + setDecayTokenFactory: ( + decayTokenFactory: string, + ) => Promise; + owner(): Promise; + decayTokenFactory(): Promise; +} + +// Function to parse addresses from addresses.txt +function parseAddressesFile(): Record { + const addressesPath = path.resolve( + __dirname, + '../../contracts/addresses.txt', + ); + const fileContent = fs.readFileSync(addressesPath, 'utf8'); + + const addresses: Record = {}; + + // Extract contract addresses using regex + const patterns = { + DecayingTokenFactory: + /DecayingTokenFactory proxy deployed to: (0x[a-fA-F0-9]{40})/, + VoteDecayTokenVotingPower: + /VoteDecayTokenVotingPower proxy deployed to: (0x[a-fA-F0-9]{40})/, + }; + + for (const [key, pattern] of Object.entries(patterns)) { + const match = fileContent.match(pattern); + if (match && match[1]) { + addresses[key] = match[1]; + } + } + + return addresses; +} + +const voteDecayTokenVotingPowerAbi = [ + { + inputs: [ + { + internalType: 'address', + name: '_decayTokenFactory', + type: 'address', + }, + ], + name: 'setDecayTokenFactory', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'decayTokenFactory', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]; + +async function main(): Promise { + // Parse addresses from file + const addresses = parseAddressesFile(); + + // Verify all required addresses are available + const requiredContracts = [ + 'DecayingTokenFactory', + 'VoteDecayTokenVotingPower', + ]; + const missingContracts = requiredContracts.filter( + (contract) => !addresses[contract], + ); + + if (missingContracts.length > 0) { + throw new Error(`Missing addresses for: ${missingContracts.join(', ')}`); + } + + // Connect to the network + const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); + + // Create a wallet instance + const wallet = new ethers.Wallet(process.env.PRIVATE_KEY || '', provider); + + // Use the VoteDecayTokenVotingPower address directly from addresses.txt + const voteDecayTokenVotingPowerAddress = + addresses['VoteDecayTokenVotingPower']; + + console.log( + 'VoteDecayTokenVotingPower address from addresses.txt:', + voteDecayTokenVotingPowerAddress, + ); + + // Get the VoteDecayTokenVotingPower contract instance + const voteDecayTokenVotingPower = new ethers.Contract( + voteDecayTokenVotingPowerAddress, + voteDecayTokenVotingPowerAbi, + wallet, + ) as ethers.Contract & VoteDecayTokenVotingPowerInterface; + + // Check if the wallet is the owner + const contractOwner = await voteDecayTokenVotingPower.owner(); + if (contractOwner.toLowerCase() !== wallet.address.toLowerCase()) { + console.error( + `Your wallet (${wallet.address}) is not the owner of the VoteDecayTokenVotingPower contract.`, + ); + console.error(`The owner is: ${contractOwner}`); + throw new Error( + 'Permission denied: only the contract owner can call setDecayTokenFactory', + ); + } + + console.log( + 'Setting decay token factory in VoteDecayTokenVotingPower with the following address:', + ); + console.log('DecayingTokenFactory:', addresses['DecayingTokenFactory']); + + try { + const tx = await voteDecayTokenVotingPower.setDecayTokenFactory( + addresses['DecayingTokenFactory'], + ); + + console.log('Transaction sent, waiting for confirmation...'); + await tx.wait(); + console.log( + 'Decay token factory set successfully in VoteDecayTokenVotingPower!', + ); + + // Retrieve and display the set decay token factory address + console.log('\nRetrieving the set decay token factory address...'); + const setDecayTokenFactoryAddress = + await voteDecayTokenVotingPower.decayTokenFactory(); + console.log( + 'Current decay token factory address:', + setDecayTokenFactoryAddress, + ); + + // Verify it matches what we set + if ( + setDecayTokenFactoryAddress.toLowerCase() === + addresses['DecayingTokenFactory'].toLowerCase() + ) { + console.log( + 'โœ… Verification successful: The retrieved address matches the one we set', + ); + } else { + console.log( + 'โŒ Verification failed: The retrieved address does not match the one we set', + ); + console.log('Expected:', addresses['DecayingTokenFactory']); + console.log('Retrieved:', setDecayTokenFactoryAddress); + } + } catch (error: any) { + console.error('Error setting decay token factory:', error.message); + throw error; + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/packages/storage-evm/scripts/base-mainnet-contracts-scripts/set-decay-voting-power-contract-in-decayingtokenfactory.ts b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/set-decay-voting-power-contract-in-decayingtokenfactory.ts index 989bdc575..055eb8162 100644 --- a/packages/storage-evm/scripts/base-mainnet-contracts-scripts/set-decay-voting-power-contract-in-decayingtokenfactory.ts +++ b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/set-decay-voting-power-contract-in-decayingtokenfactory.ts @@ -25,6 +25,7 @@ interface DecayingTokenFactoryInterface { decayVotingPowerContract: string, ) => Promise; owner(): Promise; + decayVotingPowerContract(): Promise; } // Function to parse addresses from addresses.txt @@ -82,6 +83,19 @@ const decayingTokenFactoryAbi = [ stateMutability: 'view', type: 'function', }, + { + inputs: [], + name: 'decayVotingPowerContract', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, ]; async function main(): Promise { @@ -152,6 +166,31 @@ async function main(): Promise { console.log( 'Decay voting power contract set successfully in DecayingTokenFactory!', ); + + // Retrieve and display the set decay voting power contract address + console.log('\nRetrieving the set decay voting power contract address...'); + const setDecayVotingPowerAddress = + await decayingTokenFactory.decayVotingPowerContract(); + console.log( + 'Current decay voting power contract address:', + setDecayVotingPowerAddress, + ); + + // Verify it matches what we set + if ( + setDecayVotingPowerAddress.toLowerCase() === + addresses['VoteDecayTokenVotingPower'].toLowerCase() + ) { + console.log( + 'โœ… Verification successful: The retrieved address matches the one we set', + ); + } else { + console.log( + 'โŒ Verification failed: The retrieved address does not match the one we set', + ); + console.log('Expected:', addresses['VoteDecayTokenVotingPower']); + console.log('Retrieved:', setDecayVotingPowerAddress); + } } catch (error: any) { console.error('Error setting decay voting power contract:', error.message); throw error; diff --git a/packages/storage-evm/scripts/base-mainnet-contracts-scripts/set-proposals-contract-in-decayingtokenfactory.ts b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/set-proposals-contract-in-decayingtokenfactory.ts new file mode 100644 index 000000000..fb79c2c49 --- /dev/null +++ b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/set-proposals-contract-in-decayingtokenfactory.ts @@ -0,0 +1,196 @@ +import dotenv from 'dotenv'; +import { ethers } from 'ethers'; +import fs from 'fs'; +import path from 'path'; + +dotenv.config(); + +// Add interface definitions +interface Log { + topics: string[]; + [key: string]: any; +} + +interface TransactionReceipt { + logs: Log[]; + [key: string]: any; +} + +interface ContractTransactionWithWait extends ethers.ContractTransaction { + wait(): Promise; +} + +interface DecayingTokenFactoryInterface { + setProposalsContract: ( + proposalsContract: string, + ) => Promise; + owner(): Promise; + proposalsContract(): Promise; +} + +// Function to parse addresses from addresses.txt +function parseAddressesFile(): Record { + const addressesPath = path.resolve( + __dirname, + '../../contracts/addresses.txt', + ); + const fileContent = fs.readFileSync(addressesPath, 'utf8'); + + const addresses: Record = {}; + + // Extract contract addresses using regex + const patterns = { + DecayingTokenFactory: + /DecayingTokenFactory proxy deployed to: (0x[a-fA-F0-9]{40})/, + DAOProposals: /DAOProposals deployed to: (0x[a-fA-F0-9]{40})/, + }; + + for (const [key, pattern] of Object.entries(patterns)) { + const match = fileContent.match(pattern); + if (match && match[1]) { + addresses[key] = match[1]; + } + } + + return addresses; +} + +const decayingTokenFactoryAbi = [ + { + inputs: [ + { + internalType: 'address', + name: '_proposalsContract', + type: 'address', + }, + ], + name: 'setProposalsContract', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'proposalsContract', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, +]; + +async function main(): Promise { + // Parse addresses from file + const addresses = parseAddressesFile(); + + // Verify all required addresses are available + const requiredContracts = ['DecayingTokenFactory', 'DAOProposals']; + const missingContracts = requiredContracts.filter( + (contract) => !addresses[contract], + ); + + if (missingContracts.length > 0) { + throw new Error(`Missing addresses for: ${missingContracts.join(', ')}`); + } + + // Connect to the network + const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); + + // Create a wallet instance + const wallet = new ethers.Wallet(process.env.PRIVATE_KEY || '', provider); + + // Use the DecayingTokenFactory address directly from addresses.txt + const decayingTokenFactoryAddress = addresses['DecayingTokenFactory']; + + console.log( + 'DecayingTokenFactory address from addresses.txt:', + decayingTokenFactoryAddress, + ); + + // Get the DecayingTokenFactory contract instance + const decayingTokenFactory = new ethers.Contract( + decayingTokenFactoryAddress, + decayingTokenFactoryAbi, + wallet, + ) as ethers.Contract & DecayingTokenFactoryInterface; + + // Check if the wallet is the owner + const contractOwner = await decayingTokenFactory.owner(); + if (contractOwner.toLowerCase() !== wallet.address.toLowerCase()) { + console.error( + `Your wallet (${wallet.address}) is not the owner of the DecayingTokenFactory contract.`, + ); + console.error(`The owner is: ${contractOwner}`); + throw new Error( + 'Permission denied: only the contract owner can call setProposalsContract', + ); + } + + console.log( + 'Setting proposals contract in DecayingTokenFactory with the following address:', + ); + console.log('DAOProposals:', addresses['DAOProposals']); + + try { + const tx = await decayingTokenFactory.setProposalsContract( + addresses['DAOProposals'], + ); + + console.log('Transaction sent, waiting for confirmation...'); + await tx.wait(); + console.log('Proposals contract set successfully in DecayingTokenFactory!'); + + // Retrieve and display the set proposals contract address + console.log('\nRetrieving the set proposals contract address...'); + const setProposalsContractAddress = + await decayingTokenFactory.proposalsContract(); + console.log( + 'Current proposals contract address:', + setProposalsContractAddress, + ); + + // Verify it matches what we set + if ( + setProposalsContractAddress.toLowerCase() === + addresses['DAOProposals'].toLowerCase() + ) { + console.log( + 'โœ… Verification successful: The retrieved address matches the one we set', + ); + } else { + console.log( + 'โŒ Verification failed: The retrieved address does not match the one we set', + ); + console.log('Expected:', addresses['DAOProposals']); + console.log('Retrieved:', setProposalsContractAddress); + } + } catch (error: any) { + console.error('Error setting proposals contract:', error.message); + throw error; + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/packages/storage-evm/scripts/base-mainnet-contracts-scripts/test-decaying-token-proposal.ts b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/test-decaying-token-proposal.ts new file mode 100644 index 000000000..898805435 --- /dev/null +++ b/packages/storage-evm/scripts/base-mainnet-contracts-scripts/test-decaying-token-proposal.ts @@ -0,0 +1,918 @@ +import dotenv from 'dotenv'; +import { ethers } from 'ethers'; +import fs from 'fs'; + +dotenv.config(); + +interface SpaceCreationParams { + unity: number; + quorum: number; + votingPowerSource: number; + exitMethod: number; + joinMethod: number; +} + +interface Transaction { + target: string; + value: number; + data: string | Uint8Array; +} + +interface ProposalParams { + spaceId: number; + duration: number; + transactions: Transaction[]; +} + +interface AccountData { + privateKey: string; + address: string; +} + +// Base Mainnet contract addresses from addresses.txt +const CONTRACTS = { + DAO_SPACE_FACTORY: '0xc8B8454D2F9192FeCAbc2C6F5d88F6434A2a9cd9', + DAO_PROPOSALS: '0x001bA7a00a259Fb12d7936455e292a60FC2bef14', + DECAYING_TOKEN_FACTORY: '0x299f4D2327933c1f363301dbd2a28379ccD5539b', + DECAY_TOKEN_VOTING_POWER: '0x9A1c157f8b0A8F7bFb0f6A82d69F52fAc5Bb7EfD', +}; + +// DAOSpaceFactory ABI with necessary functions +const daoSpaceFactoryAbi = [ + { + inputs: [ + { + components: [ + { internalType: 'uint256', name: 'unity', type: 'uint256' }, + { internalType: 'uint256', name: 'quorum', type: 'uint256' }, + { + internalType: 'uint256', + name: 'votingPowerSource', + type: 'uint256', + }, + { internalType: 'uint256', name: 'exitMethod', type: 'uint256' }, + { internalType: 'uint256', name: 'joinMethod', type: 'uint256' }, + ], + internalType: + 'struct DAOSpaceFactoryImplementation.SpaceCreationParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'createSpace', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '_spaceId', type: 'uint256' }], + name: 'getSpaceExecutor', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '_spaceId', type: 'uint256' }], + name: 'getSpaceMembers', + outputs: [{ internalType: 'address[]', name: '', type: 'address[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '_spaceId', type: 'uint256' }], + name: 'joinSpace', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +]; + +// DAOProposals ABI with necessary functions +const daoProposalsAbi = [ + { + inputs: [ + { + components: [ + { internalType: 'uint256', name: 'spaceId', type: 'uint256' }, + { internalType: 'uint256', name: 'duration', type: 'uint256' }, + { + components: [ + { internalType: 'address', name: 'target', type: 'address' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + internalType: 'struct IDAOProposals.Transaction[]', + name: 'transactions', + type: 'tuple[]', + }, + ], + internalType: 'struct IDAOProposals.ProposalParams', + name: 'params', + type: 'tuple', + }, + ], + name: 'createProposal', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256', name: '_proposalId', type: 'uint256' }, + { internalType: 'bool', name: '_support', type: 'bool' }, + ], + name: 'vote', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: '_proposalId', type: 'uint256' }], + name: 'getProposalCore', + outputs: [ + { internalType: 'uint256', name: 'spaceId', type: 'uint256' }, + { internalType: 'uint256', name: 'startTime', type: 'uint256' }, + { internalType: 'uint256', name: 'endTime', type: 'uint256' }, + { internalType: 'bool', name: 'executed', type: 'bool' }, + { internalType: 'bool', name: 'expired', type: 'bool' }, + { internalType: 'uint256', name: 'yesVotes', type: 'uint256' }, + { internalType: 'uint256', name: 'noVotes', type: 'uint256' }, + { + internalType: 'uint256', + name: 'totalVotingPowerAtSnapshot', + type: 'uint256', + }, + { internalType: 'address', name: 'creator', type: 'address' }, + ], + stateMutability: 'view', + type: 'function', + }, +]; + +// DecayingTokenFactory ABI +const decayingTokenFactoryAbi = [ + { + inputs: [ + { internalType: 'uint256', name: 'spaceId', type: 'uint256' }, + { internalType: 'string', name: 'name', type: 'string' }, + { internalType: 'string', name: 'symbol', type: 'string' }, + { internalType: 'uint256', name: 'maxSupply', type: 'uint256' }, + { internalType: 'bool', name: 'transferable', type: 'bool' }, + { internalType: 'bool', name: 'isVotingToken', type: 'bool' }, + { internalType: 'uint256', name: 'decayPercentage', type: 'uint256' }, + { internalType: 'uint256', name: 'decayInterval', type: 'uint256' }, + ], + name: 'deployDecayingToken', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'spaceId', type: 'uint256' }], + name: 'getSpaceToken', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'spacesContract', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'decayVotingPowerContract', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, +]; + +// DecayingSpaceToken ABI for verification +const decayingSpaceTokenAbi = [ + { + inputs: [], + name: 'name', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'decayPercentage', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'decayInterval', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +]; + +// DecayTokenVotingPower ABI +const decayTokenVotingPowerAbi = [ + { + inputs: [], + name: 'decayTokenFactory', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'spacesContract', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, +]; + +// Add these event signatures after the existing ABIs +const decayingTokenFactoryEventSignatures = { + TokenDeployed: ethers.id('TokenDeployed(uint256,address,string,string)'), + DecayingTokenParameters: ethers.id( + 'DecayingTokenParameters(address,uint256,uint256)', + ), + VotingTokenSet: ethers.id('VotingTokenSet(uint256,address)'), + SpacesContractUpdated: ethers.id('SpacesContractUpdated(address)'), + DecayVotingPowerContractUpdated: ethers.id( + 'DecayVotingPowerContractUpdated(address)', + ), +}; + +// Add function to parse DecayingTokenFactory events +function parseDecayingTokenFactoryEvents(receipt: any, factoryAddress: string) { + console.log('\n๐Ÿ” Parsing DecayingTokenFactory events from transaction...'); + + const factoryEvents = receipt.logs.filter( + (log: any) => + log.address && log.address.toLowerCase() === factoryAddress.toLowerCase(), + ); + + console.log(`Found ${factoryEvents.length} events from DecayingTokenFactory`); + + factoryEvents.forEach((log: any, index: number) => { + console.log(`\n๐Ÿ“‹ Event ${index + 1}:`); + console.log(` Address: ${log.address}`); + console.log(` Topics: ${log.topics}`); + console.log(` Data: ${log.data}`); + + // Check for specific events + const topic0 = log.topics[0]; + + if (topic0 === decayingTokenFactoryEventSignatures.TokenDeployed) { + try { + // TokenDeployed(uint256 indexed spaceId, address indexed tokenAddress, string name, string symbol) + // spaceId is in topics[1], tokenAddress is in topics[2] + const spaceId = parseInt(log.topics[1], 16); + const tokenAddress = '0x' + log.topics[2].slice(26); // Extract address from topic + + // Decode the non-indexed parameters (name, symbol) from data + const decoded = ethers.AbiCoder.defaultAbiCoder().decode( + ['string', 'string'], + log.data, + ); + + console.log(` ๐ŸŽ‰ TokenDeployed Event Found!`); + console.log(` Space ID: ${spaceId}`); + console.log(` Token Address: ${tokenAddress}`); + console.log(` Name: ${decoded[0]}`); + console.log(` Symbol: ${decoded[1]}`); + return { spaceId, tokenAddress, name: decoded[0], symbol: decoded[1] }; + } catch (error) { + console.log(` โŒ Error decoding TokenDeployed event: ${error}`); + } + } else if ( + topic0 === decayingTokenFactoryEventSignatures.DecayingTokenParameters + ) { + try { + const decoded = ethers.AbiCoder.defaultAbiCoder().decode( + ['address', 'uint256', 'uint256'], + log.data, + ); + console.log(` ๐Ÿ“Š DecayingTokenParameters Event Found!`); + console.log(` Token Address: ${decoded[0]}`); + console.log(` Decay Percentage: ${decoded[1]} basis points`); + console.log(` Decay Interval: ${decoded[2]} seconds`); + } catch (error) { + console.log( + ` โŒ Error decoding DecayingTokenParameters event: ${error}`, + ); + } + } else if (topic0 === decayingTokenFactoryEventSignatures.VotingTokenSet) { + try { + const spaceId = parseInt(log.topics[1], 16); + const tokenAddress = '0x' + log.topics[2].slice(26); + console.log(` ๐Ÿ—ณ๏ธ VotingTokenSet Event Found!`); + console.log(` Space ID: ${spaceId}`); + console.log(` Token Address: ${tokenAddress}`); + } catch (error) { + console.log(` โŒ Error decoding VotingTokenSet event: ${error}`); + } + } else { + console.log(` โ“ Unknown event with topic: ${topic0}`); + } + }); + + return null; +} + +// Add function to check for failed execution events +function checkForExecutionFailure(receipt: any) { + console.log('\n๐Ÿ” Checking for execution failure events...'); + + // Look for any revert or failure events + const allEvents = receipt.logs; + console.log(`Total logs in transaction: ${allEvents.length}`); + + allEvents.forEach((log: any, index: number) => { + console.log(`\nLog ${index + 1}:`); + console.log(` Address: ${log.address}`); + console.log(` Topics: ${log.topics.slice(0, 2)}`); // Show first 2 topics + console.log(` Data length: ${log.data.length}`); + + // Check if this might be an execution event from the proposals contract + if (log.address.toLowerCase() === CONTRACTS.DAO_PROPOSALS.toLowerCase()) { + console.log(` ๐Ÿ“ Event from DAOProposals contract`); + + // Check for execution events + const possibleExecutionEvents = [ + ethers.id('ProposalExecuted(uint256)'), + ethers.id('ProposalExecutionFailed(uint256,string)'), + ethers.id('ExecutionFailed(uint256,uint256,string)'), + ]; + + possibleExecutionEvents.forEach((eventSig) => { + if (log.topics[0] === eventSig) { + console.log(` ๐ŸŽฏ Matched known execution event: ${eventSig}`); + } + }); + } + }); +} + +async function testDecayingTokenProposal(): Promise { + console.log('๐Ÿš€ Starting decaying token proposal test...'); + console.log('\n=== CONTRACT ADDRESSES ==='); + console.log(`DAO Space Factory: ${CONTRACTS.DAO_SPACE_FACTORY}`); + console.log(`DAO Proposals: ${CONTRACTS.DAO_PROPOSALS}`); + console.log(`Decaying Token Factory: ${CONTRACTS.DECAYING_TOKEN_FACTORY}`); + + const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); + + // Load account data + let accountData: AccountData[] = []; + try { + const data = fs.readFileSync('accounts.json', 'utf8'); + if (data.trim()) { + accountData = JSON.parse(data); + } + } catch (error) { + console.log( + 'accounts.json not found or invalid. Using environment variables.', + ); + } + + // If no accounts from JSON, try to use environment variable + if (accountData.length === 0) { + const privateKey = process.env.PRIVATE_KEY; + + if (privateKey) { + console.log('Using private key from environment variable.'); + try { + // Remove 0x prefix if present + const cleanPrivateKey = privateKey.startsWith('0x') + ? privateKey.slice(2) + : privateKey; + + const wallet = new ethers.Wallet(cleanPrivateKey); + accountData = [ + { + privateKey: cleanPrivateKey, + address: wallet.address, + }, + ]; + } catch (error) { + console.error( + 'Invalid private key format in environment variable:', + error, + ); + } + } else { + console.error('PRIVATE_KEY not found in environment variables.'); + } + } + + if (accountData.length === 0) { + console.error( + 'No accounts found. Please create an accounts.json file or provide a valid PRIVATE_KEY in .env', + ); + return; + } + + const wallet = new ethers.Wallet(accountData[0].privateKey, provider); + console.log(`\n๐Ÿ’ผ Using wallet address: ${wallet.address}`); + + // Initialize contracts + const daoSpaceFactory = new ethers.Contract( + CONTRACTS.DAO_SPACE_FACTORY, + daoSpaceFactoryAbi, + wallet, + ); + + const daoProposals = new ethers.Contract( + CONTRACTS.DAO_PROPOSALS, + daoProposalsAbi, + wallet, + ); + + const decayingTokenFactory = new ethers.Contract( + CONTRACTS.DECAYING_TOKEN_FACTORY, + decayingTokenFactoryAbi, + wallet, + ); + + // Test getSpaceToken functionality first + console.log('\n=== TESTING getSpaceToken BEFORE TOKEN DEPLOYMENT ==='); + + let tokenAddressBefore = ethers.ZeroAddress; + try { + tokenAddressBefore = await decayingTokenFactory.getSpaceToken(1); // Test with space 1 + console.log(`Token address before deployment: ${tokenAddressBefore}`); + } catch (error: any) { + if (error.code === 'CALL_EXCEPTION') { + console.log( + 'โœ… Confirmed: No token deployed yet (contract reverted as expected)', + ); + tokenAddressBefore = ethers.ZeroAddress; + } else { + console.error( + 'โŒ Unexpected error calling getSpaceToken:', + error.message, + ); + throw error; + } + } + + if (tokenAddressBefore === ethers.ZeroAddress) { + console.log( + 'โœ… getSpaceToken works correctly - returns zero address when no token deployed', + ); + } else { + console.log('โš ๏ธ Unexpected: Token address found for space 1'); + } + + try { + // Step 1: Create a Space + console.log('\n๐Ÿ“ Step 1: Creating a new space...'); + const spaceParams: SpaceCreationParams = { + unity: 51, // 51% unity + quorum: 10, // 10% quorum (lower for easier testing) + votingPowerSource: 2, // Space voting power (1 member = 1 vote) + exitMethod: 1, + joinMethod: 1, + }; + + console.log( + `Creating space with unity: ${spaceParams.unity}%, quorum: ${spaceParams.quorum}%`, + ); + const tx = await daoSpaceFactory.createSpace(spaceParams); + console.log(`Space creation transaction submitted: ${tx.hash}`); + + const receipt = await tx.wait(); + console.log('โœ… Space creation transaction confirmed'); + + // Find the SpaceCreated event + const event = receipt?.logs.find( + (log) => + log.topics[0] === + ethers.id( + 'SpaceCreated(uint256,uint256,uint256,uint256,uint256,uint256,address,address)', + ), + ); + + if (!event) { + console.error('โŒ Space creation event not found in transaction receipt'); + return; + } + + const spaceId = parseInt(event.topics[1], 16); + console.log(`๐ŸŽ‰ Space created with ID: ${spaceId}`); + + // Get space executor and members + const executorAddress = await daoSpaceFactory.getSpaceExecutor(spaceId); + console.log(`๐Ÿ”‘ Space executor address: ${executorAddress}`); + + const members = await daoSpaceFactory.getSpaceMembers(spaceId); + console.log(`๐Ÿ‘ฅ Space members: ${members}`); + console.log(`โœ… Creator is member: ${members.includes(wallet.address)}`); + + // Define token parameters early so we can modify them based on contract setup + const tokenName = 'Governance Decay Token'; + const tokenSymbol = 'GDT'; + const maxSupply = 0; // Unlimited supply + const transferable = true; + let isVotingToken = true; // This might be changed based on contract setup + const decayPercentage = 500; // 5% decay per interval (in basis points) + const decayInterval = 86400; // 1 day in seconds + + // Step 2.5: Check DecayingTokenFactory configuration + console.log( + '\n๐Ÿ“ Step 2.5: Checking DecayingTokenFactory configuration...', + ); + + try { + const spacesContract = await decayingTokenFactory.spacesContract(); + console.log(`โœ… Spaces contract set to: ${spacesContract}`); + + if (spacesContract === ethers.ZeroAddress) { + console.log( + 'โŒ Spaces contract is not set - this will cause deployment to fail', + ); + return; + } + } catch (error) { + console.error('โŒ Error checking spacesContract:', error); + return; + } + + let decayVotingPowerAddress = ethers.ZeroAddress; + try { + decayVotingPowerAddress = + await decayingTokenFactory.decayVotingPowerContract(); + console.log( + `โœ… Decay voting power contract set to: ${decayVotingPowerAddress}`, + ); + + if (decayVotingPowerAddress === ethers.ZeroAddress) { + console.log('โš ๏ธ Decay voting power contract is not set'); + console.log( + ' Since isVotingToken=true, this might cause deployment to fail', + ); + console.log(' Trying with isVotingToken=false instead...'); + + // Set isVotingToken to false if no voting power contract is set + isVotingToken = false; + } + } catch (error) { + console.error('โŒ Error checking decayVotingPowerContract:', error); + console.log(' Setting isVotingToken=false to avoid issues...'); + isVotingToken = false; + } + + // Step 2.6: Check DecayTokenVotingPower configuration (reverse check) + if (decayVotingPowerAddress !== ethers.ZeroAddress) { + console.log( + '\n๐Ÿ“ Step 2.6: Checking DecayTokenVotingPower configuration...', + ); + + const decayTokenVotingPower = new ethers.Contract( + decayVotingPowerAddress, + decayTokenVotingPowerAbi, + provider, + ); + + try { + const configuredTokenFactory = + await decayTokenVotingPower.decayTokenFactory(); + console.log( + `โœ… DecayTokenVotingPower.decayTokenFactory set to: ${configuredTokenFactory}`, + ); + + if ( + configuredTokenFactory.toLowerCase() !== + CONTRACTS.DECAYING_TOKEN_FACTORY.toLowerCase() + ) { + console.log( + 'โš ๏ธ DecayTokenVotingPower.decayTokenFactory does not match our DecayingTokenFactory!', + ); + console.log(` Expected: ${CONTRACTS.DECAYING_TOKEN_FACTORY}`); + console.log(` Actual: ${configuredTokenFactory}`); + console.log(' This might cause voting token registration to fail'); + } else { + console.log( + 'โœ… DecayTokenVotingPower correctly configured with DecayingTokenFactory', + ); + } + } catch (error) { + console.error( + 'โŒ Error checking DecayTokenVotingPower.decayTokenFactory:', + error, + ); + console.log( + ' This might indicate the voting power contract is not properly deployed', + ); + } + + try { + const spacesContractAddress = + await decayTokenVotingPower.spacesContract(); + console.log( + `โœ… DecayTokenVotingPower.spacesContract set to: ${spacesContractAddress}`, + ); + + if ( + spacesContractAddress.toLowerCase() === + CONTRACTS.DAO_SPACE_FACTORY.toLowerCase() + ) { + console.log( + 'โœ… DecayTokenVotingPower correctly configured with DAOSpaceFactory', + ); + } else { + console.log('โŒ DecayTokenVotingPower spacesContract mismatch'); + console.log('Expected:', CONTRACTS.DAO_SPACE_FACTORY); + console.log('Actual:', spacesContractAddress); + } + } catch (error: any) { + console.log( + 'โš ๏ธ DecayTokenVotingPower.spacesContract() function not available', + ); + console.log( + "This is expected if the contract doesn't have a spacesContract getter", + ); + } + } + + // Step 3: Create a Proposal to deploy a decaying token + console.log('\n๐Ÿ“ Step 3: Creating proposal to deploy decaying token...'); + + console.log(`\n๐Ÿช™ Token Parameters:`); + console.log(` Name: ${tokenName}`); + console.log(` Symbol: ${tokenSymbol}`); + console.log(` Max Supply: ${maxSupply === 0 ? 'Unlimited' : maxSupply}`); + console.log(` Transferable: ${transferable}`); + console.log(` Is Voting Token: ${isVotingToken}`); + console.log(` Decay Percentage: ${decayPercentage / 100}% per interval`); + console.log(` Decay Interval: ${decayInterval / 86400} days`); + + // Test the function encoding + console.log('\n๐Ÿ” Testing function encoding...'); + try { + const testInterface = new ethers.Interface(decayingTokenFactoryAbi); + const testCalldata = testInterface.encodeFunctionData( + 'deployDecayingToken', + [ + spaceId, + tokenName, + tokenSymbol, + maxSupply, + transferable, + isVotingToken, + decayPercentage, + decayInterval, + ], + ); + console.log(`โœ… Function encoding successful`); + console.log(`๐Ÿ“‹ Full calldata: ${testCalldata}`); + + // Try to decode it back to verify + const decoded = testInterface.decodeFunctionData( + 'deployDecayingToken', + testCalldata, + ); + console.log(`โœ… Decoding verification successful`); + console.log(` Decoded spaceId: ${decoded[0]}`); + console.log(` Decoded name: ${decoded[1]}`); + console.log(` Decoded symbol: ${decoded[2]}`); + } catch (encodeError) { + console.error('โŒ Error with function encoding:', encodeError); + return; + } + + // Encode the deployDecayingToken function call + const deployTokenCalldata = + decayingTokenFactory.interface.encodeFunctionData('deployDecayingToken', [ + spaceId, + tokenName, + tokenSymbol, + maxSupply, + transferable, + isVotingToken, + decayPercentage, + decayInterval, + ]); + + console.log( + `๐Ÿ“‹ Encoded calldata length: ${deployTokenCalldata.length} characters`, + ); + console.log(`๐Ÿ“‹ Calldata: ${deployTokenCalldata.substring(0, 100)}...`); + + // Create proposal + const proposalParams: ProposalParams = { + spaceId: spaceId, + duration: 3600, // 1 hour for testing + transactions: [ + { + target: CONTRACTS.DECAYING_TOKEN_FACTORY, + value: 0, + data: deployTokenCalldata, + }, + ], + }; + + console.log('\n๐Ÿ“‹ Proposal parameters:'); + console.log(` Space ID: ${proposalParams.spaceId}`); + console.log( + ` Duration: ${proposalParams.duration} seconds (${ + proposalParams.duration / 3600 + } hours)`, + ); + console.log(` Target Contract: ${proposalParams.transactions[0].target}`); + console.log(` Value: ${proposalParams.transactions[0].value} ETH`); + + console.log('\n๐Ÿ—ณ๏ธ Submitting proposal creation transaction...'); + try { + // Estimate gas first + const estimatedGas = await daoProposals.createProposal.estimateGas( + proposalParams, + ); + console.log(`โ›ฝ Estimated gas: ${estimatedGas.toString()}`); + + // Create the proposal + const createProposalTx = await daoProposals.createProposal( + proposalParams, + { + gasLimit: Math.floor(Number(estimatedGas) * 1.2), // Add 20% buffer + }, + ); + + console.log(`๐Ÿ“ค Proposal creation tx hash: ${createProposalTx.hash}`); + + const createProposalReceipt = await createProposalTx.wait(); + console.log('โœ… Proposal creation confirmed'); + + // Find the ProposalCreated event + const proposalEvent = createProposalReceipt?.logs.find( + (log) => + log.topics[0] === + ethers.id( + 'ProposalCreated(uint256,uint256,uint256,uint256,address,bytes)', + ), + ); + + if (!proposalEvent) { + console.error('โŒ Proposal creation event not found'); + return; + } + + const proposalId = parseInt(proposalEvent.topics[1], 16); + console.log(`๐ŸŽ‰ Proposal created with ID: ${proposalId}`); + + // Step 4: Vote on the proposal + console.log('\n๐Ÿ“ Step 4: Voting on the proposal...'); + const voteTx = await daoProposals.vote(proposalId, true); // Vote YES + console.log(`๐Ÿ—ณ๏ธ Vote transaction hash: ${voteTx.hash}`); + + await voteTx.wait(); + console.log('โœ… Vote confirmed'); + + // Step 5: Check proposal status and wait for execution + console.log('\n๐Ÿ“ Step 5: Checking proposal status...'); + let proposalData = await daoProposals.getProposalCore(proposalId); + + console.log('\n๐Ÿ“Š Proposal data:'); + console.log(` Space ID: ${proposalData.spaceId}`); + console.log( + ` Start time: ${new Date( + Number(proposalData.startTime) * 1000, + ).toLocaleString()}`, + ); + console.log( + ` End time: ${new Date( + Number(proposalData.endTime) * 1000, + ).toLocaleString()}`, + ); + console.log(` Executed: ${proposalData.executed}`); + console.log(` Expired: ${proposalData.expired}`); + console.log(` Yes votes: ${proposalData.yesVotes}`); + console.log(` No votes: ${proposalData.noVotes}`); + console.log( + ` Total voting power: ${proposalData.totalVotingPowerAtSnapshot}`, + ); + console.log(` Creator: ${proposalData.creator}`); + + if (proposalData.executed) { + console.log('\n๐ŸŽ‰ SUCCESS! The proposal was executed automatically.'); + } else { + console.log('\nโณ The proposal has not been executed yet.'); + console.log( + 'This might be normal if the voting period is still active or quorum not reached.', + ); + } + + // Step 6: Test getSpaceToken AFTER proposal execution - ENHANCED + console.log('\n๐Ÿ“ Step 6: Testing UPGRADED getSpaceToken...'); + + let tokenAddressAfter = ethers.ZeroAddress; + try { + tokenAddressAfter = await decayingTokenFactory.getSpaceToken(spaceId); + console.log(`๐Ÿช™ getSpaceToken result: ${tokenAddressAfter}`); + + if (tokenAddressAfter !== ethers.ZeroAddress) { + console.log( + '๐ŸŽ‰ SUCCESS! getSpaceToken now works and found the token!', + ); + } else { + console.log('โ„น๏ธ getSpaceToken works but returns zero address'); + } + } catch (error: any) { + console.log(`โŒ getSpaceToken failed: ${error.message}`); + // Fall back to the known address from events + tokenAddressAfter = '0xc8995514f8c76b9d9a509b4fdba0d06eb732907e'; + console.log(`Using token address from event: ${tokenAddressAfter}`); + } + + // Step 7: Verify token properties using the known address + console.log('\n๐Ÿ“ Step 7: Verifying deployed token properties...'); + console.log(`Using token address from event: ${tokenAddressAfter}`); + + const token = new ethers.Contract( + tokenAddressAfter, + decayingSpaceTokenAbi, + provider, + ); + + try { + const actualName = await token.name(); + const actualSymbol = await token.symbol(); + const actualDecayPercentage = await token.decayPercentage(); + const actualDecayInterval = await token.decayInterval(); + + console.log('\nโœ… Token verification:'); + console.log(` Name: ${actualName} (expected: ${tokenName})`); + console.log(` Symbol: ${actualSymbol} (expected: ${tokenSymbol})`); + console.log( + ` Decay Percentage: ${actualDecayPercentage} basis points (expected: ${decayPercentage})`, + ); + console.log( + ` Decay Interval: ${actualDecayInterval} seconds (expected: ${decayInterval})`, + ); + + // Verify all properties match + const allMatch = + actualName === tokenName && + actualSymbol === tokenSymbol && + Number(actualDecayPercentage) === decayPercentage && + Number(actualDecayInterval) === decayInterval; + + if (allMatch) { + console.log( + '\n๐ŸŽ‰ COMPLETE SUCCESS! All token properties match exactly!', + ); + } else { + console.log( + '\nโš ๏ธ Some token properties do not match expected values.', + ); + } + } catch (tokenError) { + console.error('โŒ Error verifying token properties:', tokenError); + } + + // Final summary + console.log('\n๐Ÿ === TEST SUMMARY ==='); + console.log(`โœ… Space created with ID: ${spaceId}`); + console.log(`โœ… Proposal created with ID: ${proposalId}`); + console.log(`โœ… Vote submitted successfully`); + console.log( + `โœ… getSpaceToken tested before deployment: Contract reverted (expected)`, + ); + console.log( + `โœ… getSpaceToken tested after proposal: ${tokenAddressAfter}`, + ); + + if (tokenAddressAfter !== ethers.ZeroAddress) { + console.log(`๐ŸŽ‰ Decaying token successfully deployed via proposal!`); + } else { + console.log(`โš ๏ธ Token not deployed - check proposal execution status.`); + } + } catch (proposalError) { + console.error('\nโŒ Proposal creation or execution failed:'); + console.error(proposalError); + + // Additional debugging information + console.log('\n๐Ÿ” Debugging information:'); + console.log(`Space ID: ${spaceId}`); + console.log(`Space members: ${members}`); + console.log(`Is wallet a member: ${members.includes(wallet.address)}`); + console.log(`Wallet address: ${wallet.address}`); + } + } catch (outerError) { + console.error('\nโŒ Error in decaying token proposal test:', outerError); + } +} + +// Run the test +testDecayingTokenProposal().catch(console.error); diff --git a/packages/storage-evm/scripts/decaying-token-factory.upgrade.ts b/packages/storage-evm/scripts/decaying-token-factory.upgrade.ts index 96fca438b..89c865b0b 100644 --- a/packages/storage-evm/scripts/decaying-token-factory.upgrade.ts +++ b/packages/storage-evm/scripts/decaying-token-factory.upgrade.ts @@ -1,4 +1,7 @@ import { ethers, upgrades } from 'hardhat'; +import dotenv from 'dotenv'; + +dotenv.config(); // Replace this with your actual proxy address from addresses.txt const PROXY_ADDRESS = '0x299f4D2327933c1f363301dbd2a28379ccD5539b'; @@ -6,28 +9,110 @@ const PROXY_ADDRESS = '0x299f4D2327933c1f363301dbd2a28379ccD5539b'; async function main(): Promise { // Get the deployer's address (first account from the connected provider) const [deployer] = await ethers.getSigners(); - const adminAddress = await deployer.getAddress(); + const deployerAddress = await deployer.getAddress(); + + console.log('๐Ÿš€ Starting DecayingTokenFactory upgrade...'); + console.log('Network:', await ethers.provider.getNetwork()); + console.log('Upgrading with address:', deployerAddress); + console.log('Proxy address:', PROXY_ADDRESS); + + try { + // Get the current implementation + const DecayingTokenFactory = await ethers.getContractFactory( + 'DecayingTokenFactory', + ); + + // Check if we can read from the proxy first + console.log('\n๐Ÿ” Verifying current proxy state...'); + const currentProxy = DecayingTokenFactory.attach(PROXY_ADDRESS); + + try { + const owner = await currentProxy.owner(); + console.log('Current owner:', owner); + + if (owner.toLowerCase() !== deployerAddress.toLowerCase()) { + console.error('โŒ Deployer is not the owner of the contract!'); + console.error(`Owner: ${owner}`); + console.error(`Deployer: ${deployerAddress}`); + throw new Error('Permission denied: only owner can upgrade'); + } + } catch (error) { + console.error('โŒ Error reading current proxy:', error); + throw error; + } + + // Test if getSpaceToken function exists (should fail before upgrade) + console.log('\n๐Ÿงช Testing current getSpaceToken function...'); + try { + await currentProxy.getSpaceToken(1); + console.log( + 'โš ๏ธ getSpaceToken already exists - upgrade may not be needed', + ); + } catch (error) { + console.log('โœ… getSpaceToken missing as expected'); + } + + // Perform the upgrade + console.log('\nโฌ†๏ธ Performing upgrade...'); + + try { + // Try without force import first + const upgradedContract = await upgrades.upgradeProxy( + PROXY_ADDRESS, + DecayingTokenFactory, + ); + + await upgradedContract.waitForDeployment(); + console.log('โœ… Upgrade successful!'); + + // Test the new function + console.log('\n๐Ÿงช Testing upgraded getSpaceToken function...'); + try { + const result = await upgradedContract.getSpaceToken(1); + console.log('โœ… getSpaceToken now works! Result:', result); + } catch (error) { + console.log('โŒ getSpaceToken still failing:', error); + } + + console.log('\n๐ŸŽ‰ DecayingTokenFactory upgrade completed successfully!'); + console.log('Proxy address (unchanged):', PROXY_ADDRESS); + } catch (upgradeError: any) { + if (upgradeError.message.includes('not found')) { + console.log( + 'โš ๏ธ Proxy not found in upgrades manifest, trying force import...', + ); - console.log('Upgrading with admin address:', adminAddress); + await upgrades.forceImport(PROXY_ADDRESS, DecayingTokenFactory); + console.log('โœ… Force import successful'); - const DecayingTokenFactory = await ethers.getContractFactory( - 'DecayingTokenFactory', - ); + // Retry upgrade + const upgradedContract = await upgrades.upgradeProxy( + PROXY_ADDRESS, + DecayingTokenFactory, + ); - console.log('Force importing existing proxy...'); - await upgrades.forceImport(PROXY_ADDRESS, DecayingTokenFactory); + await upgradedContract.waitForDeployment(); + console.log('โœ… Upgrade successful after force import!'); + } else { + throw upgradeError; + } + } + } catch (error: any) { + console.error('\nโŒ Upgrade failed:', error.message); - console.log('Upgrading DecayingTokenFactory...'); - const upgradedContract = await upgrades.upgradeProxy( - PROXY_ADDRESS, - DecayingTokenFactory, - ); + // Additional debugging info + if (error.message.includes('Ownable')) { + console.error( + '๐Ÿ’ก This might be an ownership issue. Check the contract owner.', + ); + } else if (error.message.includes('proxy admin')) { + console.error( + '๐Ÿ’ก This might be a proxy admin issue. Check the admin address.', + ); + } - await upgradedContract.waitForDeployment(); - console.log( - 'DecayingTokenFactory upgraded at address:', - await upgradedContract.getAddress(), - ); + throw error; + } } main() diff --git a/packages/storage-evm/scripts/ownership-token-factory.upgrade.ts b/packages/storage-evm/scripts/ownership-token-factory.upgrade.ts index cf88abc55..ccdd98159 100644 --- a/packages/storage-evm/scripts/ownership-token-factory.upgrade.ts +++ b/packages/storage-evm/scripts/ownership-token-factory.upgrade.ts @@ -1,4 +1,7 @@ import { ethers, upgrades } from 'hardhat'; +import dotenv from 'dotenv'; + +dotenv.config(); // Replace this with your actual proxy address from addresses.txt const PROXY_ADDRESS = '0xA1eDf096B72226ae2f7BDEb12E9c9C82152BccB6'; @@ -6,28 +9,110 @@ const PROXY_ADDRESS = '0xA1eDf096B72226ae2f7BDEb12E9c9C82152BccB6'; async function main(): Promise { // Get the deployer's address (first account from the connected provider) const [deployer] = await ethers.getSigners(); - const adminAddress = await deployer.getAddress(); + const deployerAddress = await deployer.getAddress(); + + console.log('๐Ÿš€ Starting OwnershipTokenFactory upgrade...'); + console.log('Network:', await ethers.provider.getNetwork()); + console.log('Upgrading with address:', deployerAddress); + console.log('Proxy address:', PROXY_ADDRESS); + + try { + // Get the current implementation + const OwnershipTokenFactory = await ethers.getContractFactory( + 'OwnershipTokenFactory', + ); + + // Check if we can read from the proxy first + console.log('\n๐Ÿ” Verifying current proxy state...'); + const currentProxy = OwnershipTokenFactory.attach(PROXY_ADDRESS); + + try { + const owner = await currentProxy.owner(); + console.log('Current owner:', owner); + + if (owner.toLowerCase() !== deployerAddress.toLowerCase()) { + console.error('โŒ Deployer is not the owner of the contract!'); + console.error(`Owner: ${owner}`); + console.error(`Deployer: ${deployerAddress}`); + throw new Error('Permission denied: only owner can upgrade'); + } + } catch (error) { + console.error('โŒ Error reading current proxy:', error); + throw error; + } + + // Test if getSpaceToken function exists (should fail before upgrade if missing) + console.log('\n๐Ÿงช Testing current getSpaceToken function...'); + try { + await currentProxy.getSpaceToken(1); + console.log( + 'โš ๏ธ getSpaceToken already exists - upgrade may not be needed', + ); + } catch (error) { + console.log('โœ… getSpaceToken missing as expected'); + } + + // Perform the upgrade + console.log('\nโฌ†๏ธ Performing upgrade...'); + + try { + // Try without force import first + const upgradedContract = await upgrades.upgradeProxy( + PROXY_ADDRESS, + OwnershipTokenFactory, + ); + + await upgradedContract.waitForDeployment(); + console.log('โœ… Upgrade successful!'); + + // Test the new function + console.log('\n๐Ÿงช Testing upgraded getSpaceToken function...'); + try { + const result = await upgradedContract.getSpaceToken(1); + console.log('โœ… getSpaceToken now works! Result:', result); + } catch (error) { + console.log('โŒ getSpaceToken still failing:', error); + } + + console.log('\n๐ŸŽ‰ OwnershipTokenFactory upgrade completed successfully!'); + console.log('Proxy address (unchanged):', PROXY_ADDRESS); + } catch (upgradeError: any) { + if (upgradeError.message.includes('not found')) { + console.log( + 'โš ๏ธ Proxy not found in upgrades manifest, trying force import...', + ); - console.log('Upgrading with admin address:', adminAddress); + await upgrades.forceImport(PROXY_ADDRESS, OwnershipTokenFactory); + console.log('โœ… Force import successful'); - const OwnershipTokenFactory = await ethers.getContractFactory( - 'OwnershipTokenFactory', - ); + // Retry upgrade + const upgradedContract = await upgrades.upgradeProxy( + PROXY_ADDRESS, + OwnershipTokenFactory, + ); - console.log('Force importing existing proxy...'); - await upgrades.forceImport(PROXY_ADDRESS, OwnershipTokenFactory); + await upgradedContract.waitForDeployment(); + console.log('โœ… Upgrade successful after force import!'); + } else { + throw upgradeError; + } + } + } catch (error: any) { + console.error('\nโŒ Upgrade failed:', error.message); - console.log('Upgrading OwnershipTokenFactory...'); - const upgradedContract = await upgrades.upgradeProxy( - PROXY_ADDRESS, - OwnershipTokenFactory, - ); + // Additional debugging info + if (error.message.includes('Ownable')) { + console.error( + '๐Ÿ’ก This might be an ownership issue. Check the contract owner.', + ); + } else if (error.message.includes('proxy admin')) { + console.error( + '๐Ÿ’ก This might be a proxy admin issue. Check the admin address.', + ); + } - await upgradedContract.waitForDeployment(); - console.log( - 'OwnershipTokenFactory upgraded at address:', - await upgradedContract.getAddress(), - ); + throw error; + } } main() diff --git a/packages/storage-evm/scripts/ownership-token-voting-power-proxy.deploy.ts b/packages/storage-evm/scripts/ownership-token-voting-power-proxy.deploy.ts new file mode 100644 index 000000000..23a46b5b8 --- /dev/null +++ b/packages/storage-evm/scripts/ownership-token-voting-power-proxy.deploy.ts @@ -0,0 +1,35 @@ +import { ethers, upgrades } from 'hardhat'; + +async function main(): Promise { + const [deployer] = await ethers.getSigners(); + const adminAddress = await deployer.getAddress(); + + console.log('Deploying with admin address:', adminAddress); + + const OwnershipTokenVotingPower = await ethers.getContractFactory( + 'OwnershipTokenVotingPowerImplementation', + ); + console.log('Deploying OwnershipTokenVotingPower...'); + + const ownershipTokenVotingPower = await upgrades.deployProxy( + OwnershipTokenVotingPower, + [adminAddress], + { + initializer: 'initialize', + kind: 'uups', + }, + ); + + await ownershipTokenVotingPower.waitForDeployment(); + console.log( + 'OwnershipTokenVotingPowerImplementation proxy deployed to:', + await ownershipTokenVotingPower.getAddress(), + ); +} + +main() + .then(() => process.exit(0)) + .catch((error: Error) => { + console.error(error); + process.exit(1); + }); diff --git a/packages/storage-evm/scripts/test-upgraded-getspacetoken.ts b/packages/storage-evm/scripts/test-upgraded-getspacetoken.ts new file mode 100644 index 000000000..a4889ee0e --- /dev/null +++ b/packages/storage-evm/scripts/test-upgraded-getspacetoken.ts @@ -0,0 +1,47 @@ +import { ethers } from 'hardhat'; + +const PROXY_ADDRESS = '0x299f4D2327933c1f363301dbd2a28379ccD5539b'; + +async function main(): Promise { + const DecayingTokenFactory = await ethers.getContractFactory( + 'DecayingTokenFactory', + ); + const contract = DecayingTokenFactory.attach(PROXY_ADDRESS); + + console.log('๐Ÿงช Testing getSpaceToken with real space IDs...'); + + // Test with space ID 121 (from your successful deployment) + try { + const result = await contract.getSpaceToken(121); + console.log('โœ… Space ID 121 token address:', result); + + if (result !== ethers.ZeroAddress) { + console.log('๐ŸŽ‰ SUCCESS! Token found for space 121'); + } else { + console.log('โ„น๏ธ No token deployed for space 121 (returns zero address)'); + } + } catch (error: any) { + console.error('โŒ Space ID 121 failed:', error.message); + } + + // Test with space ID 1 (should return zero address, not revert) + try { + const result = await contract.getSpaceToken(1); + console.log('โœ… Space ID 1 token address:', result); + } catch (error: any) { + console.error('โŒ Space ID 1 still failing:', error.message); + + // This suggests there might be an interface issue + console.log('๐Ÿ” This might indicate an interface or override issue'); + } + + // Test with space ID 0 + try { + const result = await contract.getSpaceToken(0); + console.log('โœ… Space ID 0 token address:', result); + } catch (error: any) { + console.error('โŒ Space ID 0 failed:', error.message); + } +} + +main().catch(console.error); diff --git a/packages/storage-evm/scripts/verify-decaying-token-factory.ts b/packages/storage-evm/scripts/verify-decaying-token-factory.ts new file mode 100644 index 000000000..d3b89c367 --- /dev/null +++ b/packages/storage-evm/scripts/verify-decaying-token-factory.ts @@ -0,0 +1,57 @@ +import { ethers } from 'hardhat'; + +const PROXY_ADDRESS = '0x299f4D2327933c1f363301dbd2a28379ccD5539b'; + +async function main(): Promise { + const [deployer] = await ethers.getSigners(); + + console.log('๐Ÿ” Verifying DecayingTokenFactory at:', PROXY_ADDRESS); + console.log('Network:', await ethers.provider.getNetwork()); + console.log('Checking with address:', await deployer.getAddress()); + + // Get the contract with full ABI + const DecayingTokenFactory = await ethers.getContractFactory( + 'DecayingTokenFactory', + ); + const contract = DecayingTokenFactory.attach(PROXY_ADDRESS); + + // Check basic functions + try { + const owner = await contract.owner(); + console.log('โœ… Owner:', owner); + } catch (error) { + console.error('โŒ Error reading owner:', error); + } + + try { + const spacesContract = await contract.spacesContract(); + console.log('โœ… Spaces contract:', spacesContract); + } catch (error) { + console.error('โŒ Error reading spacesContract:', error); + } + + // Test the problematic function + try { + const result = await contract.getSpaceToken(1); + console.log('โœ… getSpaceToken works! Result:', result); + } catch (error: any) { + console.error('โŒ getSpaceToken failed:', error.message); + + // Try low-level call + const functionSelector = '0x1080fa43'; + const spaceIdParam = ethers.zeroPadValue(ethers.toBeHex(1), 32); + const calldata = functionSelector + spaceIdParam.slice(2); + + try { + const lowLevelResult = await ethers.provider.call({ + to: PROXY_ADDRESS, + data: calldata, + }); + console.log('Low-level call result:', lowLevelResult); + } catch (lowLevelError) { + console.log('Low-level call also failed - function definitely missing'); + } + } +} + +main().catch(console.error); diff --git a/packages/storage-evm/test/DAOSpaceFactoryImplementation.test.ts b/packages/storage-evm/test/DAOSpaceFactoryImplementation.test.ts index 6c4ddb3d3..b548eb3fc 100644 --- a/packages/storage-evm/test/DAOSpaceFactoryImplementation.test.ts +++ b/packages/storage-evm/test/DAOSpaceFactoryImplementation.test.ts @@ -1069,6 +1069,250 @@ describe('DAOSpaceFactoryImplementation', function () { ethers.parseUnits('1', 15), ); }); + + it('Should retrieve deployed decaying token address using getSpaceToken', async function () { + const { decayingTokenFactory, spaceHelper, owner, voter1 } = + await loadFixture(deployFixture); + + console.log('\n=== TESTING DECAYING TOKEN FACTORY getSpaceToken ==='); + + // Create space + const spaceParams = { + name: 'GetSpaceToken Test Space', + description: 'Testing getSpaceToken function', + imageUrl: 'https://test.com/image.png', + unity: 51, + quorum: 51, + votingPowerSource: 1, + exitMethod: 1, + joinMethod: 1, + createToken: false, + tokenName: '', + tokenSymbol: '', + }; + + await spaceHelper.contract.createSpace(spaceParams); + const spaceId = (await spaceHelper.contract.spaceCounter()).toString(); + console.log(`โœ… Created space with ID: ${spaceId}`); + + // Get the executor + const executorAddress = await spaceHelper.contract.getSpaceExecutor( + spaceId, + ); + console.log(`โœ… Space executor: ${executorAddress}`); + + // Impersonate the executor + await ethers.provider.send('hardhat_impersonateAccount', [ + executorAddress, + ]); + const executorSigner = await ethers.getSigner(executorAddress); + + // Fund the executor + await owner.sendTransaction({ + to: executorAddress, + value: ethers.parseEther('1.0'), + }); + + // Test getSpaceToken BEFORE deploying any token (should return zero address) + console.log('\n--- Testing getSpaceToken BEFORE token deployment ---'); + const tokenAddressBeforeDeployment = + await decayingTokenFactory.getSpaceToken(spaceId); + console.log( + `โŒ Token address before deployment: ${tokenAddressBeforeDeployment}`, + ); + expect(tokenAddressBeforeDeployment).to.equal(ethers.ZeroAddress); + + // Define decay parameters + const decayPercentage = 1000; // 10% decay per interval + const decayInterval = 3600; // 1 hour in seconds + const tokenName = 'Test Decay Token'; + const tokenSymbol = 'TDT'; + + console.log('\n--- Deploying decaying token ---'); + console.log(`Token name: ${tokenName}`); + console.log(`Token symbol: ${tokenSymbol}`); + console.log(`Decay percentage: ${decayPercentage / 100}%`); + console.log(`Decay interval: ${decayInterval} seconds`); + + // Deploy decaying token through the executor + const deployTx = await decayingTokenFactory + .connect(executorSigner) + .deployDecayingToken( + spaceId, + tokenName, + tokenSymbol, + 0, // maxSupply (0 = unlimited) + true, // transferable + true, // isVotingToken + decayPercentage, + decayInterval, + ); + + const receipt = await deployTx.wait(); + const tokenDeployedEvent = receipt?.logs + .filter((log) => { + try { + return ( + decayingTokenFactory.interface.parseLog({ + topics: log.topics as string[], + data: log.data, + })?.name === 'TokenDeployed' + ); + } catch (_unused) { + return false; + } + }) + .map((log) => + decayingTokenFactory.interface.parseLog({ + topics: log.topics as string[], + data: log.data, + }), + )[0]; + + if (!tokenDeployedEvent) { + throw new Error('Token deployment event not found'); + } + + const deployedTokenAddress = tokenDeployedEvent.args.tokenAddress; + console.log(`โœ… Token deployed at address: ${deployedTokenAddress}`); + + // Test getSpaceToken AFTER deploying the token + console.log('\n--- Testing getSpaceToken AFTER token deployment ---'); + const retrievedTokenAddress = await decayingTokenFactory.getSpaceToken( + spaceId, + ); + console.log(`โœ… Retrieved token address: ${retrievedTokenAddress}`); + + // Verify the addresses match + expect(retrievedTokenAddress).to.equal(deployedTokenAddress); + expect(retrievedTokenAddress).to.not.equal(ethers.ZeroAddress); + + console.log(`๐ŸŽ‰ SUCCESS: Deployed address matches retrieved address!`); + + // Get the token contract and verify its properties + const decayToken = await ethers.getContractAt( + 'DecayingSpaceToken', + retrievedTokenAddress, + ); + + console.log('\n--- Verifying token properties ---'); + const actualName = await decayToken.name(); + const actualSymbol = await decayToken.symbol(); + const actualDecayPercentage = await decayToken.decayPercentage(); + const actualDecayInterval = await decayToken.decayInterval(); + + console.log(`Token name: ${actualName}`); + console.log(`Token symbol: ${actualSymbol}`); + console.log( + `Decay percentage: ${actualDecayPercentage} (${ + Number(actualDecayPercentage) / 100 + }%)`, + ); + console.log(`Decay interval: ${actualDecayInterval} seconds`); + + expect(actualName).to.equal(tokenName); + expect(actualSymbol).to.equal(tokenSymbol); + expect(actualDecayPercentage).to.equal(decayPercentage); + expect(actualDecayInterval).to.equal(decayInterval); + + // Test with multiple spaces to ensure each gets its own token + console.log('\n--- Testing with multiple spaces ---'); + + // Create second space + await spaceHelper.contract.createSpace({ + ...spaceParams, + name: 'Second Space', + }); + const spaceId2 = (await spaceHelper.contract.spaceCounter()).toString(); + console.log(`โœ… Created second space with ID: ${spaceId2}`); + + // Get executor for second space + const executorAddress2 = await spaceHelper.contract.getSpaceExecutor( + spaceId2, + ); + await ethers.provider.send('hardhat_impersonateAccount', [ + executorAddress2, + ]); + const executorSigner2 = await ethers.getSigner(executorAddress2); + await owner.sendTransaction({ + to: executorAddress2, + value: ethers.parseEther('1.0'), + }); + + // Deploy token for second space + const deployTx2 = await decayingTokenFactory + .connect(executorSigner2) + .deployDecayingToken( + spaceId2, + 'Second Decay Token', + 'SDT', + 0, + true, + true, + 2000, // Different decay percentage + 7200, // Different decay interval + ); + + const receipt2 = await deployTx2.wait(); + const tokenDeployedEvent2 = receipt2?.logs + .filter((log) => { + try { + return ( + decayingTokenFactory.interface.parseLog({ + topics: log.topics as string[], + data: log.data, + })?.name === 'TokenDeployed' + ); + } catch (_unused) { + return false; + } + }) + .map((log) => + decayingTokenFactory.interface.parseLog({ + topics: log.topics as string[], + data: log.data, + }), + )[0]; + + if (!tokenDeployedEvent2) { + throw new Error('Second token deployment event not found'); + } + + const deployedTokenAddress2 = tokenDeployedEvent2.args.tokenAddress; + console.log( + `โœ… Second token deployed at address: ${deployedTokenAddress2}`, + ); + + // Verify both spaces have different tokens + const retrievedTokenAddress1 = await decayingTokenFactory.getSpaceToken( + spaceId, + ); + const retrievedTokenAddress2 = await decayingTokenFactory.getSpaceToken( + spaceId2, + ); + + console.log(`Space ${spaceId} token address: ${retrievedTokenAddress1}`); + console.log(`Space ${spaceId2} token address: ${retrievedTokenAddress2}`); + + expect(retrievedTokenAddress1).to.equal(deployedTokenAddress); + expect(retrievedTokenAddress2).to.equal(deployedTokenAddress2); + expect(retrievedTokenAddress1).to.not.equal(retrievedTokenAddress2); + + console.log(`๐ŸŽ‰ SUCCESS: Each space has its own unique token!`); + + // Test querying non-existent space + console.log('\n--- Testing with non-existent space ---'); + const nonExistentSpaceId = 999; + const nonExistentTokenAddress = await decayingTokenFactory.getSpaceToken( + nonExistentSpaceId, + ); + console.log( + `Token address for non-existent space ${nonExistentSpaceId}: ${nonExistentTokenAddress}`, + ); + expect(nonExistentTokenAddress).to.equal(ethers.ZeroAddress); + + console.log('๐ŸŽ‰ All getSpaceToken tests passed!'); + }); }); describe('Enhanced Decay Token Tests', function () { diff --git a/path/to/file b/path/to/file new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/path/to/file @@ -0,0 +1 @@ + \ No newline at end of file