diff --git a/packages/cactus-plugin-persistence-ethereum/src/main/json/contract-abi/ERC1155.json b/packages/cactus-plugin-persistence-ethereum/src/main/json/contract-abi/ERC1155.json new file mode 100644 index 0000000000..6e12eafba0 --- /dev/null +++ b/packages/cactus-plugin-persistence-ethereum/src/main/json/contract-abi/ERC1155.json @@ -0,0 +1,579 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ERC1155InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC1155InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "idsLength", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "valuesLength", + "type": "uint256" + } + ], + "name": "ERC1155InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "ERC1155InvalidOperator", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC1155InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC1155InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC1155MissingApprovalForAll", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "TransferBatch", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TransferSingle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "URI", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mintBatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeBatchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "tokenURI", + "type": "string" + } + ], + "name": "setTokenURI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + } + ], + "name": "balanceOfBatch", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "uri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/packages/cactus-plugin-persistence-ethereum/src/main/json/openapi.json b/packages/cactus-plugin-persistence-ethereum/src/main/json/openapi.json index e78c9af42c..8996ef416a 100644 --- a/packages/cactus-plugin-persistence-ethereum/src/main/json/openapi.json +++ b/packages/cactus-plugin-persistence-ethereum/src/main/json/openapi.json @@ -13,12 +13,13 @@ "schemas": { "TokenTypeV1": { "type": "string", - "enum": ["erc20", "erc721"], + "enum": ["erc20", "erc721", "erc1155"], "x-enum-descriptions": [ "EIP-20: Token Standard", - "EIP-721: Non-Fungible Token Standard" + "EIP-721: Non-Fungible Token Standard", + "EIP-1155: Multi Token Standard" ], - "x-enum-varnames": ["ERC20", "ERC721"] + "x-enum-varnames": ["ERC20", "ERC721", "ERC1155"] }, "MonitoredToken": { "description": "Ethereum tokens that are being monitored by the persistence plugin.", diff --git a/packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql b/packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql index 97926e32c2..fed1c44539 100644 --- a/packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql +++ b/packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql @@ -147,6 +147,87 @@ CREATE POLICY token_metadata_erc721_delete ON ethereum."token_metadata_erc721" FOR DELETE TO anon, authenticated, service_role USING (true); +-- Table: ethereum.token_metadata_erc1155 + +-- DROP TABLE IF EXISTS ethereum.token_metadata_erc1155; + +CREATE TABLE IF NOT EXISTS ethereum.token_metadata_erc1155 +( + address text COLLATE pg_catalog."default" NOT NULL, + name text COLLATE pg_catalog."default", + symbol text COLLATE pg_catalog."default", + created_at timestamp with time zone NOT NULL DEFAULT now(), + CONSTRAINT token_metadata_erc1155_pkey PRIMARY KEY (address), + CONSTRAINT token_metadata_erc1155_address_key UNIQUE (address) +); + +ALTER TABLE IF EXISTS ethereum.token_metadata_erc1155 + OWNER to postgres; + +ALTER TABLE ethereum.token_metadata_erc1155 +ENABLE ROW LEVEL SECURITY; + +CREATE POLICY token_metadata_erc1155_select ON ethereum.token_metadata_erc1155 +FOR SELECT TO anon, authenticated, service_role +USING (true); + +CREATE POLICY token_metadata_erc1155_insert ON ethereum.token_metadata_erc1155 +FOR INSERT TO anon, authenticated, service_role +WITH CHECK (true); + +CREATE POLICY token_metadata_erc1155_update ON ethereum.token_metadata_erc1155 +FOR UPDATE TO anon, authenticated, service_role +USING (true) +WITH CHECK (true); + +CREATE POLICY token_metadata_erc1155_delete ON ethereum.token_metadata_erc1155 +FOR DELETE TO anon, authenticated, service_role +USING (true); + +-- Table: ethereum.token_erc1155 + +-- DROP TABLE IF EXISTS ethereum.token_erc1155; + +CREATE TABLE IF NOT EXISTS ethereum.token_erc1155 +( + id uuid DEFAULT uuid_generate_v4() NOT NULL, + account_address text COLLATE pg_catalog."default" NOT NULL, + token_address text COLLATE pg_catalog."default" NOT NULL, + token_id numeric NOT NULL, + balance numeric NOT NULL DEFAULT 0, + uri text COLLATE pg_catalog."default", + last_balance_change timestamp without time zone NOT NULL DEFAULT now(), + CONSTRAINT token_erc1155_pkey PRIMARY KEY (id), + CONSTRAINT token_erc1155_contract_token_account_unique UNIQUE (token_address, token_id, account_address), + CONSTRAINT token_erc1155_token_address_fkey FOREIGN KEY (token_address) + REFERENCES ethereum.token_metadata_erc1155 (address) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION +); + +ALTER TABLE IF EXISTS ethereum.token_erc1155 + OWNER to postgres; + +ALTER TABLE ethereum.token_erc1155 +ENABLE ROW LEVEL SECURITY; + +CREATE POLICY token_erc1155_select ON ethereum.token_erc1155 +FOR SELECT TO anon, authenticated, service_role +USING (true); + +CREATE POLICY token_erc1155_insert ON ethereum.token_erc1155 +FOR INSERT TO anon, authenticated, service_role +WITH CHECK (true); + +CREATE POLICY token_erc1155_update ON ethereum.token_erc1155 +FOR UPDATE TO anon, authenticated, service_role +USING (true) +WITH CHECK (true); + +CREATE POLICY token_erc1155_delete ON ethereum.token_erc1155 +FOR DELETE TO anon, authenticated, service_role +USING (true); + -- Table: ethereum.token_erc721 -- DROP TABLE IF EXISTS ethereum.token_erc721; @@ -277,8 +358,15 @@ CREATE POLICY token_transfer_delete ON ethereum.token_transfer FOR DELETE TO anon, authenticated, service_role USING (true); +-- Adding token_id column to token_transfer for ERC-1155 support +ALTER TABLE ethereum.token_transfer +ADD COLUMN IF NOT EXISTS token_id numeric; + COMMENT ON COLUMN ethereum.token_transfer.value - IS 'ERC20 - token quantity, ERC721 - token ID'; + IS 'ERC20 - token quantity, ERC721 - not used (see token_id), ERC1155 - token quantity'; + +COMMENT ON COLUMN ethereum.token_transfer.token_id + IS 'ERC721 - token ID, ERC1155 - token ID, ERC20 - not used'; ---------------------------------------------------------------------------------------------------- -- VIEWS @@ -329,6 +417,31 @@ WITH (security_invoker = on) ALTER TABLE ethereum.erc721_token_history_view OWNER TO postgres; +-- View: ethereum.erc1155_token_history_view + +-- DROP VIEW IF EXISTS ethereum.erc1155_token_history_view; + +CREATE OR REPLACE VIEW ethereum.erc1155_token_history_view +WITH (security_invoker = on) +AS +SELECT + tx.hash AS transaction_hash, + tx."to" AS token_address, + b.created_at, + tt.sender, + tt.recipient, + tt.token_id, + tt.value AS quantity +FROM ethereum.transaction tx +JOIN ethereum.block b ON tx.block_number = b.number +JOIN ethereum.token_transfer tt ON tx.id = tt.transaction_id +JOIN ethereum.token_metadata_erc1155 tkn ON tx."to" = tkn.address +WHERE tt.token_id IS NOT NULL -- Ensure token_id is present (ERC-1155 or ERC-721) +ORDER BY b.created_at, tt.recipient; + +ALTER TABLE ethereum.erc1155_token_history_view + OWNER TO postgres; + -- View: ethereum.token_erc20 -- DROP MATERIALIZED VIEW IF EXISTS ethereum.token_erc20; @@ -460,6 +573,104 @@ ALTER PROCEDURE ethereum.update_issued_erc721_tokens(numeric) GRANT EXECUTE ON PROCEDURE ethereum.update_issued_erc721_tokens(numeric) TO public; +-- PROCEDURE: ethereum.update_issued_erc1155_tokens(numeric) + +-- DROP PROCEDURE IF EXISTS ethereum.update_issued_erc1155_tokens(numeric); + +CREATE OR REPLACE PROCEDURE ethereum.update_issued_erc1155_tokens(IN from_block_number numeric) +LANGUAGE 'plpgsql' +SET search_path = ethereum, public +AS $BODY$ +DECLARE + current_token_entry ethereum.token_erc1155%ROWTYPE; + token_transfer record; + block_created_at timestamp; +BEGIN + -- Get the block's creation time + SELECT created_at + FROM ethereum.block + WHERE number = from_block_number + INTO block_created_at; + + IF NOT found THEN + raise exception 'invalid block provided: %', from_block_number + USING hint = 'ensure that given block was synchronized correctly'; + END IF; + + -- Process each transfer (distinct by token_address, token_id, recipient to handle batch transfers) + FOR token_transfer IN SELECT + * + FROM ethereum.erc1155_token_history_view + WHERE created_at >= block_created_at + ORDER BY token_address, token_id, sender, recipient, created_at DESC + LOOP + -- Process sender (decrease balance) + IF token_transfer.sender != '0x0000000000000000000000000000000000000000' THEN -- Not a mint + SELECT * FROM ethereum.token_erc1155 + WHERE token_id = token_transfer.token_id + AND token_address = token_transfer.token_address + AND account_address = token_transfer.sender + INTO current_token_entry; + + IF found THEN + -- Decrease sender's balance + UPDATE ethereum.token_erc1155 + SET balance = GREATEST(0, balance - token_transfer.quantity), + last_balance_change = token_transfer.created_at + WHERE id = current_token_entry.id; + + -- Remove entry if balance reaches 0 + DELETE FROM ethereum.token_erc1155 + WHERE id = current_token_entry.id AND balance = 0; + END IF; + END IF; + + -- Process recipient (increase balance) + IF token_transfer.recipient != '0x0000000000000000000000000000000000000000' THEN -- Not a burn + SELECT * FROM ethereum.token_erc1155 + WHERE token_id = token_transfer.token_id + AND token_address = token_transfer.token_address + AND account_address = token_transfer.recipient + INTO current_token_entry; + + IF NOT found THEN + -- Create new entry for recipient + raise notice 'create entry for token ID % on contract % for account %', + token_transfer.token_id, token_transfer.token_address, token_transfer.recipient; + INSERT INTO ethereum.token_erc1155 + VALUES ( + uuid_generate_v4(), + token_transfer.recipient, + token_transfer.token_address, + token_transfer.token_id, + token_transfer.quantity, + NULL, -- uri (can be fetched later) + token_transfer.created_at + ); + ELSE + -- Increase recipient's balance + IF current_token_entry.last_balance_change < token_transfer.created_at THEN + raise notice 'update balance for token ID % on contract % for account %', + token_transfer.token_id, token_transfer.token_address, token_transfer.recipient; + UPDATE ethereum.token_erc1155 + SET balance = balance + token_transfer.quantity, + last_balance_change = token_transfer.created_at + WHERE id = current_token_entry.id; + ELSE + raise notice 'current entry is more recent - ignore token ID % on contract % for account %', + token_transfer.token_id, token_transfer.token_address, token_transfer.recipient; + END IF; + END IF; + END IF; + END LOOP; +END +$BODY$; + +ALTER PROCEDURE ethereum.update_issued_erc1155_tokens(numeric) + OWNER TO postgres; + +GRANT EXECUTE ON PROCEDURE ethereum.update_issued_erc1155_tokens(numeric) TO public; + -- FUNCTION: ethereum.get_missing_blocks_in_range(integer, integer) -- DROP FUNCTION IF EXISTS ethereum.get_missing_blocks_in_range(integer, integer); diff --git a/packages/cactus-plugin-persistence-ethereum/src/main/typescript/db-client/database.types.ts b/packages/cactus-plugin-persistence-ethereum/src/main/typescript/db-client/database.types.ts index 61f626d6e5..af50a130ea 100644 --- a/packages/cactus-plugin-persistence-ethereum/src/main/typescript/db-client/database.types.ts +++ b/packages/cactus-plugin-persistence-ethereum/src/main/typescript/db-client/database.types.ts @@ -81,6 +81,35 @@ export interface Database { last_owner_change?: string; }; }; + token_erc1155: { + Row: { + id: string; + account_address: string; + token_address: string; + token_id: number; + balance: number; + uri: string | null; + last_balance_change: string; + }; + Insert: { + id?: string; + account_address: string; + token_address: string; + token_id: number; + balance: number; + uri?: string | null; + last_balance_change?: string; + }; + Update: { + id?: string; + account_address?: string; + token_address?: string; + token_id?: number; + balance: number; + uri?: string | null; + last_balance_change?: string; + }; + }; token_metadata_erc20: { Row: { address: string; @@ -124,6 +153,26 @@ export interface Database { created_at?: string; }; }; + token_metadata_erc1155: { + Row: { + address: string; + name: string | null; + symbol: string | null; + created_at: string; + }; + Insert: { + address: string; + name: string | null; + symbol: string | null; + created_at?: string; + }; + Update: { + address?: string; + name?: string | null; + symbol?: string | null; + created_at?: string; + }; + }; token_transfer: { Row: { transaction_id: string; diff --git a/packages/cactus-plugin-persistence-ethereum/src/main/typescript/db-client/db-client.ts b/packages/cactus-plugin-persistence-ethereum/src/main/typescript/db-client/db-client.ts index c7be13df7b..b1284a61de 100644 --- a/packages/cactus-plugin-persistence-ethereum/src/main/typescript/db-client/db-client.ts +++ b/packages/cactus-plugin-persistence-ethereum/src/main/typescript/db-client/db-client.ts @@ -35,6 +35,12 @@ type TokenMetadataERC20InsertType = type TokenMetadataERC721RowType = SchemaTables["token_metadata_erc721"]["Row"]; type TokenMetadataERC721InsertType = SchemaTables["token_metadata_erc721"]["Insert"]; +type TokenMetadataERC1155RowType = + SchemaTables["token_metadata_erc1155"]["Row"]; +type TokenMetadataERC1155InsertType = + SchemaTables["token_metadata_erc1155"]["Insert"]; +type TokenERC1155RowType = SchemaTables["token_erc1155"]["Row"]; +type TokenERC1155InsertType = SchemaTables["token_erc1155"]["Insert"]; type SchemaFunctions = DatabaseSchemaType["ethereum"]["Functions"]; type GetMissingBlocksInRangeReturnType = @@ -50,7 +56,9 @@ type TokenERC20RowType = { export type BlockDataTransferInput = Omit< TokenTransferInsertType, "transaction_id" ->; +> & { + token_id?: number; // Added for ERC-1155 and ERC-721 as ERC-20 do not use it +}; export type BlockDataTransactionInput = Omit< TransactionInsertType, @@ -270,6 +278,24 @@ export default class PostgresDatabaseClient { return queryResponse.rows; } + /** + * Read all ERC1155 token metadata. + * @returns ERC1155 token metadata + */ + public async getTokenMetadataERC1155(): Promise< + TokenMetadataERC1155RowType[] + > { + this.assertConnected(); + + const queryResponse = await this.client.query( + "SELECT * FROM ethereum.token_metadata_erc1155", + ); + this.log.debug( + `Received ${queryResponse.rowCount} rows from table token_metadata_erc1155`, + ); + return queryResponse.rows; + } + /** * Insert new ERC721 token metadata into the database. * @param token ERC721 token metadata @@ -289,6 +315,55 @@ export default class PostgresDatabaseClient { ); } + /** + * Insert new ERC1155 token metadata into the database. + * @param token ERC1155 token metadata + */ + public async insertTokenMetadataERC1155( + token: TokenMetadataERC1155InsertType, + ): Promise { + this.assertConnected(); + + this.log.debug("Insert ERC1155 token metadata:", token); + const insertResponse = await this.client.query( + `INSERT INTO ethereum.token_metadata_erc1155("address", "name", "symbol") + VALUES ($1, $2, $3)`, + [token.address, token.name, token.symbol], + ); + this.log.info( + `Inserted ${insertResponse.rowCount} rows into table token_metadata_erc1155`, + ); + } + + /** + * Insert or update data of issued ERC1155 token. + * @param token ERC1155 token data + */ + public async upsertTokenERC1155( + token: TokenERC1155InsertType, + ): Promise { + this.assertConnected(); + + this.log.debug("Insert ERC1155 token if not present yet:", token); + const insertResponse = await this.client.query( + `INSERT INTO ethereum.token_erc1155("account_address", "token_address", "token_id", "balance", "uri") + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT ON CONSTRAINT token_erc1155_contract_token_account_unique + DO UPDATE SET balance = EXCLUDED.balance, + last_balance_change = NOW();`, + [ + token.account_address, + token.token_address, + token.token_id, + token.balance, + token.uri || null, + ], + ); + this.log.debug( + `Inserted/Updated ${insertResponse.rowCount} rows into table token_erc1155`, + ); + } + /** * Insert or update data of issued ERC721 token. * @param token ERC721 token data. @@ -343,6 +418,27 @@ export default class PostgresDatabaseClient { return queryResponse.rows; } + /** + * Read all issued ERC1155 tokens for a specific account. + * @param accountAddress The address of the account to query tokens for + * @returns ERC1155 tokens owned by the account + */ + public async getTokenERC1155ForAccount( + accountAddress: string, + ): Promise { + this.assertConnected(); + + const queryResponse = await this.client.query( + `SELECT * FROM ethereum.token_erc1155 + WHERE account_address = $1 AND balance > 0`, + [accountAddress], + ); + this.log.debug( + `Received ${queryResponse.rowCount} rows from table token_erc1155 for account ${accountAddress}`, + ); + return queryResponse.rows; + } + /** * Synchronize current ERC20 token balances in the DB. */ @@ -372,6 +468,23 @@ export default class PostgresDatabaseClient { this.log.debug("Calling update_issued_erc721_tokens procedure done."); } + /** + * Synchronize current ERC1155 token balances in the DB. + * @param fromBlockNumber Block number from which token transfers should be checked (for performance reasons) + */ + public async syncTokenBalanceERC1155(fromBlockNumber: number): Promise { + this.assertConnected(); + this.log.debug( + "Call update_issued_erc1155_tokens from block", + fromBlockNumber, + ); + + await this.client.query("CALL ethereum.update_issued_erc1155_tokens($1);", [ + fromBlockNumber, + ]); + this.log.debug("Calling update_issued_erc1155_tokens procedure done."); + } + /** * Read block data. Throws if block was not found. * @@ -413,7 +526,7 @@ export default class PostgresDatabaseClient { this.log.debug("Insert new block", block); const blockInsertResponse = await this.client.query( `INSERT INTO ethereum.block("number", "created_at", "hash", "number_of_tx") - VALUES ($1, $2, $3, $4)`, + VALUES ($1, $2, $3, $4)`, [block.number, block.created_at, block.hash, block.number_of_tx], ); if (blockInsertResponse.rowCount !== 1) { @@ -424,9 +537,9 @@ export default class PostgresDatabaseClient { this.log.debug("Insert new transaction", tx); const txInsertResponse = await this.client.query( `INSERT INTO - ethereum.transaction("index", "hash", "block_number", "from", "to", "eth_value", "method_signature", "method_name") - VALUES ($1, $2, $3, $4, $5, $6, $7, $8) - RETURNING id;`, + ethereum.transaction("index", "hash", "block_number", "from", "to", "eth_value", "method_signature", "method_name") + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING id;`, [ tx.index, tx.hash, @@ -450,9 +563,15 @@ export default class PostgresDatabaseClient { this.log.debug("Insert new token transfer", transfer); const transInsertResponse = await this.client.query( `INSERT INTO - ethereum.token_transfer("transaction_id", "sender", "recipient", "value") - VALUES ($1, $2, $3, $4)`, - [txId, transfer.sender, transfer.recipient, transfer.value], + ethereum.token_transfer("transaction_id", "sender", "recipient", "value", "token_id") + VALUES ($1, $2, $3, $4, $5)`, + [ + txId, + transfer.sender, + transfer.recipient, + transfer.value, + transfer.token_id || null, // token_id is optional (null for ERC-20) + ], ); if (transInsertResponse.rowCount !== 1) { throw new Error( @@ -461,6 +580,8 @@ export default class PostgresDatabaseClient { } } } + await this.syncTokenBalanceERC721(block.number); + await this.syncTokenBalanceERC1155(block.number); await this.client.query("COMMIT"); } catch (err: unknown) { diff --git a/packages/cactus-plugin-persistence-ethereum/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-persistence-ethereum/src/main/typescript/generated/openapi/typescript-axios/api.ts index 1811a4ef80..d6fdd37728 100644 --- a/packages/cactus-plugin-persistence-ethereum/src/main/typescript/generated/openapi/typescript-axios/api.ts +++ b/packages/cactus-plugin-persistence-ethereum/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -132,7 +132,11 @@ export const TokenTypeV1 = { /** * EIP-721: Non-Fungible Token Standard */ - ERC721: 'erc721' + ERC721: 'erc721', + /** + * EIP-1155: Multi Token Standard + */ + ERC1155: 'erc1155' } as const; export type TokenTypeV1 = typeof TokenTypeV1[keyof typeof TokenTypeV1]; diff --git a/packages/cactus-plugin-persistence-ethereum/src/main/typescript/plugin-persistence-ethereum.ts b/packages/cactus-plugin-persistence-ethereum/src/main/typescript/plugin-persistence-ethereum.ts index ec70cde3fc..b435a443b5 100644 --- a/packages/cactus-plugin-persistence-ethereum/src/main/typescript/plugin-persistence-ethereum.ts +++ b/packages/cactus-plugin-persistence-ethereum/src/main/typescript/plugin-persistence-ethereum.ts @@ -23,6 +23,8 @@ import ERC20 from "../json/contract-abi/ERC20.json"; import TokenClientERC20 from "./token-client/token-client-erc20"; import ERC721 from "../json/contract-abi/ERC721.json"; import TokenClientERC721 from "./token-client/token-client-erc721"; +import ERC1155 from "../json/contract-abi/ERC1155.json"; +import TokenClientERC1155 from "./token-client/token-client-erc1155"; import OAS from "../json/openapi.json"; import { getRuntimeErrorCause, normalizeAddress } from "./utils"; import { StatusEndpointV1 } from "./web-services/status-endpoint-v1"; @@ -74,6 +76,7 @@ export class PluginPersistenceEthereum private endpoints: IWebServiceEndpoint[] | undefined; private ethersInterfaceERC721 = new EthersInterface(ERC721.abi); private ethersInterfaceERC20 = new EthersInterface(ERC20.abi); + private ethersInterfaceERC1155 = new EthersInterface(ERC1155); private pushBlockMutex = new Mutex(); private syncBlocksMutex = new Mutex(); private syncTokenBalancesMutex = new Mutex(); @@ -152,6 +155,8 @@ export class PluginPersistenceEthereum return this.ethersInterfaceERC20; case TokenTypeV1.ERC721: return this.ethersInterfaceERC721; + case TokenTypeV1.ERC1155: + return this.ethersInterfaceERC1155; default: const unknownTokenType: never = tokenType; throw new Error( @@ -398,6 +403,12 @@ export class PluginPersistenceEthereum recipient: normalizeAddress(t.args["to"]), value: t.args["tokenId"].toString(), }; + case TokenTypeV1.ERC1155: + return { + sender: normalizeAddress(t.args["from"]), + recipient: normalizeAddress(t.args["to"]), + value: t.args["tokenId"].toString(), + }; default: const unknownTokenType: never = tokenType; throw new Error( @@ -461,6 +472,7 @@ export class PluginPersistenceEthereum this.log.debug("Update token balances from block", oldestBlockRestored); await this.dbClient.syncTokenBalanceERC20(); await this.dbClient.syncTokenBalanceERC721(oldestBlockRestored); + await this.dbClient.syncTokenBalanceERC1155(oldestBlockRestored); } else { this.log.warn( "Ledger not in sync (some blocks are missing), token balance not updated!", @@ -805,6 +817,35 @@ export class PluginPersistenceEthereum } } + /** + * Add new ERC1155 token to be monitored by this plugin. + * @param address ERC1155 contract address. + */ + public async addTokenERC1155(address: string): Promise { + const checkedAddress = normalizeAddress(address); + this.log.info( + "Add ERC1155 token to monitor changes on it. Address:", + checkedAddress, + ); + + const tokenClient = new TokenClientERC1155(this.apiClient, checkedAddress); + + try { + await this.dbClient.insertTokenMetadataERC1155({ + address: checkedAddress, + name: await tokenClient.name(), + symbol: await tokenClient.symbol(), + }); + } catch (err: unknown) { + throw new RuntimeError( + `Could not store ERC1155 token metadata information`, + getRuntimeErrorCause(err), + ); + } + + await this.refreshMonitoredTokens(); + } + /** * Add new ERC20 token to be monitored by this plugin. * @param address ERC20 contract address. diff --git a/packages/cactus-plugin-persistence-ethereum/src/main/typescript/token-client/token-client-erc1155.ts b/packages/cactus-plugin-persistence-ethereum/src/main/typescript/token-client/token-client-erc1155.ts new file mode 100644 index 0000000000..00f22dbec3 --- /dev/null +++ b/packages/cactus-plugin-persistence-ethereum/src/main/typescript/token-client/token-client-erc1155.ts @@ -0,0 +1,115 @@ +/** + * Client for calling methods on ERC1155 token contract. + */ + +import { EthereumApiClient } from "@hyperledger/cactus-plugin-ledger-connector-ethereum"; +import type { LogLevelDesc } from "@hyperledger/cactus-common"; +import TokenClient from "./base-token-client"; +import ERC1155 from "../../json/contract-abi/ERC1155.json"; +import { RuntimeError } from "run-time-error-cjs"; + +/** + * Client for calling methods on ERC1155 token contract. + */ +export default class TokenClientERC1155 extends TokenClient { + public static readonly CLASS_NAME: string = "TokenClientERC1155"; + + /** + * Get this class name + * @returns class name string + */ + getClassName(): string { + return TokenClientERC1155.CLASS_NAME; + } + + constructor( + apiClient: EthereumApiClient, + public address: string, + logLevel: LogLevelDesc = "info", + ) { + super( + apiClient, + { abi: ERC1155, contractName: "ERC1155", bytecode: "0x" }, + address, + logLevel, + ); + this.log.debug("TokenClientERC1155 created"); + } + + /** + * Internal method for writing consistent log message describing operation executed. + * + * @param methodName Name of the method called. + */ + private logOperation(methodName: string): void { + this.log.info( + `Call '${methodName}' on ERC1155 contract at ${this.contract.contractAddress}`, + ); + } + + /** + * Get name of the token (optional for ERC1155, may not be implemented). + * @returns token name or empty string if not implemented + */ + async name(): Promise { + this.logOperation("name"); + + try { + const response = await this.callContractMethod("name"); + if (typeof response !== "string") { + this.log.warn( + `Unexpected response type for 'name': ${response}, returning empty string`, + ); + return ""; + } + return response; + } catch (error) { + this.log.warn( + `Method 'name' not implemented on ERC1155 contract at ${this.address}, returning empty string`, + ); + return ""; + } + } + + /** + * Get symbol of the token (optional for ERC1155, may not be implemented). + * @returns token symbol or empty string if not implemented + */ + async symbol(): Promise { + this.logOperation("symbol"); + + try { + const response = await this.callContractMethod("symbol"); + if (typeof response !== "string") { + this.log.warn( + `Unexpected response type for 'symbol': ${response}, returning empty string`, + ); + return ""; + } + return response; + } catch (error) { + this.log.warn( + `Method 'symbol' not implemented on ERC1155 contract at ${this.address}, returning empty string`, + ); + return ""; + } + } + + /** + * Get URI of a token. + * @param tokenId token id to check + * @returns URI of the token + */ + async uri(tokenId: number): Promise { + this.logOperation("uri"); + + const response = await this.callContractMethod("uri", tokenId); + if (typeof response !== "string") { + throw new RuntimeError( + `Unexpected response type received for method 'uri': ${response}`, + ); + } + + return response; + } +} diff --git a/packages/cactus-plugin-persistence-ethereum/src/test/solidity/TestERC1155.json b/packages/cactus-plugin-persistence-ethereum/src/test/solidity/TestERC1155.json new file mode 100644 index 0000000000..2a1d9bf2a5 --- /dev/null +++ b/packages/cactus-plugin-persistence-ethereum/src/test/solidity/TestERC1155.json @@ -0,0 +1,578 @@ +{ + "bytecode": { + "object": "608060405234801562000010575f80fd5b503360405180602001604052805f8152506200003281620000be60201b60201c565b505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603620000a6575f6040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081526004016200009d9190620001d9565b60405180910390fd5b620000b781620000d360201b60201c565b506200053c565b8060029081620000cf919062000458565b5050565b5f60035f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508160035f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f620001c18262000196565b9050919050565b620001d381620001b5565b82525050565b5f602082019050620001ee5f830184620001c8565b92915050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200027057607f821691505b6020821081036200028657620002856200022b565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620002ea7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002ad565b620002f68683620002ad565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620003406200033a62000334846200030e565b62000317565b6200030e565b9050919050565b5f819050919050565b6200035b8362000320565b620003736200036a8262000347565b848454620002b9565b825550505050565b5f90565b620003896200037b565b6200039681848462000350565b505050565b5b81811015620003bd57620003b15f826200037f565b6001810190506200039c565b5050565b601f8211156200040c57620003d6816200028c565b620003e1846200029e565b81016020851015620003f1578190505b6200040962000400856200029e565b8301826200039b565b50505b505050565b5f82821c905092915050565b5f6200042e5f198460080262000411565b1980831691505092915050565b5f6200044883836200041d565b9150826002028217905092915050565b6200046382620001f4565b67ffffffffffffffff8111156200047f576200047e620001fe565b5b6200048b825462000258565b62000498828285620003c1565b5f60209050601f831160018114620004ce575f8415620004b9578287015190505b620004c585826200043b565b86555062000534565b601f198416620004de866200028c565b5f5b828110156200050757848901518255600182019150602085019450602081019050620004e0565b8683101562000527578489015162000523601f8916826200041d565b8355505b6001600288020188555050505b505050505050565b6128bd806200054a5f395ff3fe6080604052600436106100df575f3560e01c8063715018a61161007e578063a22cb46511610058578063a22cb465146102ba578063e985e9c5146102e2578063f242432a1461031e578063f2fde38b14610346576100e6565b8063715018a614610252578063731133e9146102685780638da5cb5b14610290576100e6565b8063162094c4116100ba578063162094c41461019e5780631f7fdffa146101c65780632eb2c2d6146101ee5780634e1273f414610216576100e6565b8062fdd58e146100ea57806301ffc9a7146101265780630e89341c14610162576100e6565b366100e657005b5f80fd5b3480156100f5575f80fd5b50610110600480360381019061010b9190611863565b61036e565b60405161011d91906118b0565b60405180910390f35b348015610131575f80fd5b5061014c6004803603810190610147919061191e565b6103c3565b6040516101599190611963565b60405180910390f35b34801561016d575f80fd5b506101886004803603810190610183919061197c565b6104a4565b6040516101959190611a31565b60405180910390f35b3480156101a9575f80fd5b506101c460048036038101906101bf9190611b7d565b610545565b005b3480156101d1575f80fd5b506101ec60048036038101906101e79190611d39565b6105a8565b005b3480156101f9575f80fd5b50610214600480360381019061020f9190611df1565b6105c2565b005b348015610221575f80fd5b5061023c60048036038101906102379190611f7c565b610669565b60405161024991906120a9565b60405180910390f35b34801561025d575f80fd5b50610266610776565b005b348015610273575f80fd5b5061028e600480360381019061028991906120c9565b610789565b005b34801561029b575f80fd5b506102a46107a3565b6040516102b19190612158565b60405180910390f35b3480156102c5575f80fd5b506102e060048036038101906102db919061219b565b6107cb565b005b3480156102ed575f80fd5b50610308600480360381019061030391906121d9565b6107e1565b6040516103159190611963565b60405180910390f35b348015610329575f80fd5b50610344600480360381019061033f9190612217565b61086f565b005b348015610351575f80fd5b5061036c600480360381019061036791906122aa565b610916565b005b5f805f8381526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f7fd9b67a26000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061048d57507f0e89341c000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061049d575061049c8261099a565b5b9050919050565b606060045f8381526020019081526020015f2080546104c290612302565b80601f01602080910402602001604051908101604052809291908181526020018280546104ee90612302565b80156105395780601f1061051057610100808354040283529160200191610539565b820191905f5260205f20905b81548152906001019060200180831161051c57829003601f168201915b50505050509050919050565b61054d610a03565b8060045f8481526020019081526020015f20908161056b91906124cf565b50817f6bb7ff708619ba0610cba295a58592e0451dee2622938c8755667688daf3529b8260405161059c9190611a31565b60405180910390a25050565b6105b0610a03565b6105bc84848484610a8a565b50505050565b5f6105cb610b0d565b90508073ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614158015610610575061060e86826107e1565b155b156106545780866040517fe237d92200000000000000000000000000000000000000000000000000000000815260040161064b92919061259e565b60405180910390fd5b6106618686868686610b14565b505050505050565b606081518351146106b557815183516040517f5b0599910000000000000000000000000000000000000000000000000000000081526004016106ac9291906125c5565b60405180910390fd5b5f835167ffffffffffffffff8111156106d1576106d0611a59565b5b6040519080825280602002602001820160405280156106ff5781602001602082028036833780820191505090505b5090505f5b845181101561076b5761073b6107238287610c0890919063ffffffff16565b6107368387610c1b90919063ffffffff16565b61036e565b82828151811061074e5761074d6125ec565b5b6020026020010181815250508061076490612646565b9050610704565b508091505092915050565b61077e610a03565b6107875f610c2e565b565b610791610a03565b61079d84848484610cf1565b50505050565b5f60035f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6107dd6107d6610b0d565b8383610d86565b5050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f610878610b0d565b90508073ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16141580156108bd57506108bb86826107e1565b155b156109015780866040517fe237d9220000000000000000000000000000000000000000000000000000000081526004016108f892919061259e565b60405180910390fd5b61090e8686868686610eef565b505050505050565b61091e610a03565b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361098e575f6040517f1e4fbdf70000000000000000000000000000000000000000000000000000000081526004016109859190612158565b60405180910390fd5b61099781610c2e565b50565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b610a0b610b0d565b73ffffffffffffffffffffffffffffffffffffffff16610a296107a3565b73ffffffffffffffffffffffffffffffffffffffff1614610a8857610a4c610b0d565b6040517f118cdaa7000000000000000000000000000000000000000000000000000000008152600401610a7f9190612158565b60405180910390fd5b565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610afa575f6040517f57f447ce000000000000000000000000000000000000000000000000000000008152600401610af19190612158565b60405180910390fd5b610b075f85858585610ff5565b50505050565b5f33905090565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610b84575f6040517f57f447ce000000000000000000000000000000000000000000000000000000008152600401610b7b9190612158565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610bf4575f6040517f01a83514000000000000000000000000000000000000000000000000000000008152600401610beb9190612158565b60405180910390fd5b610c018585858585610ff5565b5050505050565b5f60208202602084010151905092915050565b5f60208202602084010151905092915050565b5f60035f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508160035f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610d61575f6040517f57f447ce000000000000000000000000000000000000000000000000000000008152600401610d589190612158565b60405180910390fd5b5f80610d6d85856110a1565b91509150610d7e5f87848487610ff5565b505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610df6575f6040517fced3e100000000000000000000000000000000000000000000000000000000008152600401610ded9190612158565b60405180910390fd5b8060015f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610ee29190611963565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610f5f575f6040517f57f447ce000000000000000000000000000000000000000000000000000000008152600401610f569190612158565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610fcf575f6040517f01a83514000000000000000000000000000000000000000000000000000000008152600401610fc69190612158565b60405180910390fd5b5f80610fdb85856110a1565b91509150610fec8787848487610ff5565b50505050505050565b611001858585856110d1565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161461109a575f61103d610b0d565b90506001845103611089575f61105c5f86610c1b90919063ffffffff16565b90505f6110725f86610c1b90919063ffffffff16565b9050611082838989858589611467565b5050611098565b611097818787878787611616565b5b505b5050505050565b60608060405191506001825283602083015260408201905060018152826020820152604081016040529250929050565b805182511461111b57815181516040517f5b0599910000000000000000000000000000000000000000000000000000000081526004016111129291906125c5565b60405180910390fd5b5f611124610b0d565b90505f5b8351811015611326575f6111458286610c1b90919063ffffffff16565b90505f61115b8386610c1b90919063ffffffff16565b90505f73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff161461127e575f805f8481526020019081526020015f205f8a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205490508181101561122a57888183856040517f03dee4c5000000000000000000000000000000000000000000000000000000008152600401611221949392919061268d565b60405180910390fd5b8181035f808581526020019081526020015f205f8b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff161461131357805f808481526020019081526020015f205f8973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461130b91906126d0565b925050819055505b50508061131f90612646565b9050611128565b5060018351036113e1575f6113445f85610c1b90919063ffffffff16565b90505f61135a5f85610c1b90919063ffffffff16565b90508573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f6285856040516113d29291906125c5565b60405180910390a45050611460565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8686604051611457929190612703565b60405180910390a45b5050505050565b5f8473ffffffffffffffffffffffffffffffffffffffff163b111561160e578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b81526004016114c795949392919061278a565b6020604051808303815f875af192505050801561150257506040513d601f19601f820116820180604052508101906114ff91906127f6565b60015b611583573d805f8114611530576040519150601f19603f3d011682016040523d82523d5f602084013e611535565b606091505b505f81510361157b57846040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016115729190612158565b60405180910390fd5b805181602001fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461160c57846040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016116039190612158565b60405180910390fd5b505b505050505050565b5f8473ffffffffffffffffffffffffffffffffffffffff163b11156117bd578373ffffffffffffffffffffffffffffffffffffffff1663bc197c8187878686866040518663ffffffff1660e01b8152600401611676959493929190612821565b6020604051808303815f875af19250505080156116b157506040513d601f19601f820116820180604052508101906116ae91906127f6565b60015b611732573d805f81146116df576040519150601f19603f3d011682016040523d82523d5f602084013e6116e4565b606091505b505f81510361172a57846040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016117219190612158565b60405180910390fd5b805181602001fd5b63bc197c8160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916146117bb57846040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016117b29190612158565b60405180910390fd5b505b505050505050565b5f604051905090565b5f80fd5b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6117ff826117d6565b9050919050565b61180f816117f5565b8114611819575f80fd5b50565b5f8135905061182a81611806565b92915050565b5f819050919050565b61184281611830565b811461184c575f80fd5b50565b5f8135905061185d81611839565b92915050565b5f8060408385031215611879576118786117ce565b5b5f6118868582860161181c565b92505060206118978582860161184f565b9150509250929050565b6118aa81611830565b82525050565b5f6020820190506118c35f8301846118a1565b92915050565b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6118fd816118c9565b8114611907575f80fd5b50565b5f81359050611918816118f4565b92915050565b5f60208284031215611933576119326117ce565b5b5f6119408482850161190a565b91505092915050565b5f8115159050919050565b61195d81611949565b82525050565b5f6020820190506119765f830184611954565b92915050565b5f60208284031215611991576119906117ce565b5b5f61199e8482850161184f565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156119de5780820151818401526020810190506119c3565b5f8484015250505050565b5f601f19601f8301169050919050565b5f611a03826119a7565b611a0d81856119b1565b9350611a1d8185602086016119c1565b611a26816119e9565b840191505092915050565b5f6020820190508181035f830152611a4981846119f9565b905092915050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b611a8f826119e9565b810181811067ffffffffffffffff82111715611aae57611aad611a59565b5b80604052505050565b5f611ac06117c5565b9050611acc8282611a86565b919050565b5f67ffffffffffffffff821115611aeb57611aea611a59565b5b611af4826119e9565b9050602081019050919050565b828183375f83830152505050565b5f611b21611b1c84611ad1565b611ab7565b905082815260208101848484011115611b3d57611b3c611a55565b5b611b48848285611b01565b509392505050565b5f82601f830112611b6457611b63611a51565b5b8135611b74848260208601611b0f565b91505092915050565b5f8060408385031215611b9357611b926117ce565b5b5f611ba08582860161184f565b925050602083013567ffffffffffffffff811115611bc157611bc06117d2565b5b611bcd85828601611b50565b9150509250929050565b5f67ffffffffffffffff821115611bf157611bf0611a59565b5b602082029050602081019050919050565b5f80fd5b5f611c18611c1384611bd7565b611ab7565b90508083825260208201905060208402830185811115611c3b57611c3a611c02565b5b835b81811015611c645780611c50888261184f565b845260208401935050602081019050611c3d565b5050509392505050565b5f82601f830112611c8257611c81611a51565b5b8135611c92848260208601611c06565b91505092915050565b5f67ffffffffffffffff821115611cb557611cb4611a59565b5b611cbe826119e9565b9050602081019050919050565b5f611cdd611cd884611c9b565b611ab7565b905082815260208101848484011115611cf957611cf8611a55565b5b611d04848285611b01565b509392505050565b5f82601f830112611d2057611d1f611a51565b5b8135611d30848260208601611ccb565b91505092915050565b5f805f8060808587031215611d5157611d506117ce565b5b5f611d5e8782880161181c565b945050602085013567ffffffffffffffff811115611d7f57611d7e6117d2565b5b611d8b87828801611c6e565b935050604085013567ffffffffffffffff811115611dac57611dab6117d2565b5b611db887828801611c6e565b925050606085013567ffffffffffffffff811115611dd957611dd86117d2565b5b611de587828801611d0c565b91505092959194509250565b5f805f805f60a08688031215611e0a57611e096117ce565b5b5f611e178882890161181c565b9550506020611e288882890161181c565b945050604086013567ffffffffffffffff811115611e4957611e486117d2565b5b611e5588828901611c6e565b935050606086013567ffffffffffffffff811115611e7657611e756117d2565b5b611e8288828901611c6e565b925050608086013567ffffffffffffffff811115611ea357611ea26117d2565b5b611eaf88828901611d0c565b9150509295509295909350565b5f67ffffffffffffffff821115611ed657611ed5611a59565b5b602082029050602081019050919050565b5f611ef9611ef484611ebc565b611ab7565b90508083825260208201905060208402830185811115611f1c57611f1b611c02565b5b835b81811015611f455780611f31888261181c565b845260208401935050602081019050611f1e565b5050509392505050565b5f82601f830112611f6357611f62611a51565b5b8135611f73848260208601611ee7565b91505092915050565b5f8060408385031215611f9257611f916117ce565b5b5f83013567ffffffffffffffff811115611faf57611fae6117d2565b5b611fbb85828601611f4f565b925050602083013567ffffffffffffffff811115611fdc57611fdb6117d2565b5b611fe885828601611c6e565b9150509250929050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b61202481611830565b82525050565b5f612035838361201b565b60208301905092915050565b5f602082019050919050565b5f61205782611ff2565b6120618185611ffc565b935061206c8361200c565b805f5b8381101561209c578151612083888261202a565b975061208e83612041565b92505060018101905061206f565b5085935050505092915050565b5f6020820190508181035f8301526120c1818461204d565b905092915050565b5f805f80608085870312156120e1576120e06117ce565b5b5f6120ee8782880161181c565b94505060206120ff8782880161184f565b93505060406121108782880161184f565b925050606085013567ffffffffffffffff811115612131576121306117d2565b5b61213d87828801611d0c565b91505092959194509250565b612152816117f5565b82525050565b5f60208201905061216b5f830184612149565b92915050565b61217a81611949565b8114612184575f80fd5b50565b5f8135905061219581612171565b92915050565b5f80604083850312156121b1576121b06117ce565b5b5f6121be8582860161181c565b92505060206121cf85828601612187565b9150509250929050565b5f80604083850312156121ef576121ee6117ce565b5b5f6121fc8582860161181c565b925050602061220d8582860161181c565b9150509250929050565b5f805f805f60a086880312156122305761222f6117ce565b5b5f61223d8882890161181c565b955050602061224e8882890161181c565b945050604061225f8882890161184f565b93505060606122708882890161184f565b925050608086013567ffffffffffffffff811115612291576122906117d2565b5b61229d88828901611d0c565b9150509295509295909350565b5f602082840312156122bf576122be6117ce565b5b5f6122cc8482850161181c565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061231957607f821691505b60208210810361232c5761232b6122d5565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f6008830261238e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82612353565b6123988683612353565b95508019841693508086168417925050509392505050565b5f819050919050565b5f6123d36123ce6123c984611830565b6123b0565b611830565b9050919050565b5f819050919050565b6123ec836123b9565b6124006123f8826123da565b84845461235f565b825550505050565b5f90565b612414612408565b61241f8184846123e3565b505050565b5b81811015612442576124375f8261240c565b600181019050612425565b5050565b601f8211156124875761245881612332565b61246184612344565b81016020851015612470578190505b61248461247c85612344565b830182612424565b50505b505050565b5f82821c905092915050565b5f6124a75f198460080261248c565b1980831691505092915050565b5f6124bf8383612498565b9150826002028217905092915050565b6124d8826119a7565b67ffffffffffffffff8111156124f1576124f0611a59565b5b6124fb8254612302565b612506828285612446565b5f60209050601f831160018114612537575f8415612525578287015190505b61252f85826124b4565b865550612596565b601f19841661254586612332565b5f5b8281101561256c57848901518255600182019150602085019450602081019050612547565b868310156125895784890151612585601f891682612498565b8355505b6001600288020188555050505b505050505050565b5f6040820190506125b15f830185612149565b6125be6020830184612149565b9392505050565b5f6040820190506125d85f8301856118a1565b6125e560208301846118a1565b9392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61265082611830565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361268257612681612619565b5b600182019050919050565b5f6080820190506126a05f830187612149565b6126ad60208301866118a1565b6126ba60408301856118a1565b6126c760608301846118a1565b95945050505050565b5f6126da82611830565b91506126e583611830565b92508282019050808211156126fd576126fc612619565b5b92915050565b5f6040820190508181035f83015261271b818561204d565b9050818103602083015261272f818461204d565b90509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f61275c82612738565b6127668185612742565b93506127768185602086016119c1565b61277f816119e9565b840191505092915050565b5f60a08201905061279d5f830188612149565b6127aa6020830187612149565b6127b760408301866118a1565b6127c460608301856118a1565b81810360808301526127d68184612752565b90509695505050505050565b5f815190506127f0816118f4565b92915050565b5f6020828403121561280b5761280a6117ce565b5b5f612818848285016127e2565b91505092915050565b5f60a0820190506128345f830188612149565b6128416020830187612149565b8181036040830152612853818661204d565b90508181036060830152612867818561204d565b9050818103608083015261287b8184612752565b9050969550505050505056fea26469706673582212200adddf3373e12ac5ed41c9e151ae40ea7e2df0b554e6fcb0784c4009d34b6b2964736f6c63430008140033" + }, + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ERC1155InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC1155InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "idsLength", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "valuesLength", + "type": "uint256" + } + ], + "name": "ERC1155InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "ERC1155InvalidOperator", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC1155InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC1155InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC1155MissingApprovalForAll", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "TransferBatch", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TransferSingle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "URI", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + } + ], + "name": "balanceOfBatch", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mintBatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeBatchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "tokenURI", + "type": "string" + } + ], + "name": "setTokenURI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "uri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] +} \ No newline at end of file diff --git a/packages/cactus-plugin-persistence-ethereum/src/test/typescript/integration/persistence-ethereum-functional.test.ts b/packages/cactus-plugin-persistence-ethereum/src/test/typescript/integration/persistence-ethereum-functional.test.ts index 9705eb63e5..0914c4e994 100644 --- a/packages/cactus-plugin-persistence-ethereum/src/test/typescript/integration/persistence-ethereum-functional.test.ts +++ b/packages/cactus-plugin-persistence-ethereum/src/test/typescript/integration/persistence-ethereum-functional.test.ts @@ -17,6 +17,8 @@ const erc20TokenSymbol = "T20"; const erc20TokenSupply = 1000; const erc721TokenName = "TestErc721Token"; const erc721TokenSymbol = "T721"; +const erc1155TokenName = "TestERC1155Token"; +const erc1155TokenSymbol = "T1155"; // Geth environment const containerImageName = "ghcr.io/hyperledger/cacti-geth-all-in-one"; @@ -66,6 +68,7 @@ const DatabaseClientMock = DatabaseClient as unknown as jest.Mock; import { PluginPersistenceEthereum } from "../../../main/typescript"; import TestERC20ContractJson from "../../solidity/TestERC20.json"; import TestERC721ContractJson from "../../solidity/TestERC721.json"; +import TestERC1155ContractJson from "../../solidity/TestERC1155.json"; // Logger setup const log: Logger = LoggerProvider.getOrCreate({ @@ -84,6 +87,7 @@ describe("Ethereum persistence plugin tests", () => { let defaultAccountAddress: string; let erc20ContractCreationReceipt: Required; let erc721ContractCreationReceipt: Required; + let erc1155ContractCreationReceipt: Required; const expressAppConnector = express(); expressAppConnector.use(bodyParser.json({ limit: "250mb" })); @@ -152,6 +156,32 @@ describe("Ethereum persistence plugin tests", () => { } } + async function mintErc1155Token( + targetAddress: string, + tokenId: number, + amount: number, + data: string, + ) { + log.info( + `Mint ERC1155 token ID ${tokenId} for address ${targetAddress} of amount ${amount} by ${defaultAccountAddress}`, + ); + const tokenContract = new web3.eth.Contract( + TestERC1155ContractJson.abi, + erc1155ContractCreationReceipt.contractAddress, + ); + const mintResponse = await (tokenContract.methods as any) + .mint(targetAddress, tokenId, amount, data) + .send({ + from: defaultAccountAddress, + gas: 8000000, + }); + log.debug("mintErc1155Token mintResponse:", mintResponse); + expect(mintResponse).toBeTruthy(); + expect(Number(mintResponse.status)).toEqual(1); + + return mintResponse; + } + /** * For some reasons Contract doesn't detect additional (non whale) identities even if they are in web3js wallet. * Because of that we sign and send transaction manually. @@ -224,6 +254,14 @@ describe("Ethereum persistence plugin tests", () => { created_at: "2022-1-1T12:00:01Z", }, ]); + (dbClientInstance.getTokenMetadataERC1155 as jest.Mock).mockReturnValue([ + { + address: erc1155ContractCreationReceipt.contractAddress, + name: erc1155TokenName, + symbol: erc1155TokenSymbol, + created_at: "2022-1-1T12:00:01Z", + }, + ]); } /** @@ -396,6 +434,18 @@ describe("Ethereum persistence plugin tests", () => { "ERC721 deployed contract address:", erc721ContractCreationReceipt.contractAddress, ); + + const erc1155ByteCode = TestERC1155ContractJson.bytecode.object; + log.debug("starting deploying the erc1155"); + log.info("starting deploying the erc1155"); + erc1155ContractCreationReceipt = await deploySmartContract( + TestERC1155ContractJson.abi, + erc1155ByteCode, + ); + log.info( + "ERC1155 deployed contract address:", + erc1155ContractCreationReceipt.contractAddress, + ); }); test("onPluginInit creates DB schema and fetches the monitored tokens", async () => { @@ -485,6 +535,24 @@ describe("Ethereum persistence plugin tests", () => { expect(token.symbol).toEqual(erc721TokenSymbol); }); + test("Adding ERC1155 tokens to GUI test", async () => { + await persistence.addTokenERC1155( + erc1155ContractCreationReceipt.contractAddress, + ); + const insertCalls = + dbClientInstance.insertTokenMetadataERC1155.mock.calls; + expect(insertCalls.length).toBe(1); + const insertCallArgs = insertCalls[0]; + const token = insertCallArgs[0]; + expect(token).toBeTruthy(); + expect(token.address.toLowerCase()).toEqual( + erc1155ContractCreationReceipt.contractAddress.toLowerCase(), + ); + expect(checkAddressCheckSum(token.address)).toBeTrue(); + expect(token.name).toEqual(erc1155TokenName); + expect(token.symbol).toEqual(erc1155TokenSymbol); + }); + test("Refresh tokens updates internal state", async () => { const tokens = await persistence.refreshMonitoredTokens(); @@ -533,6 +601,16 @@ describe("Ethereum persistence plugin tests", () => { erc721ContractCreationReceipt.contractAddress, ); + const erc1155ByteCode = TestERC1155ContractJson.bytecode.object; + erc1155ContractCreationReceipt = await deploySmartContract( + TestERC1155ContractJson.abi, + erc1155ByteCode, + ); + log.info( + "ERC1155 deployed contract address:", + erc1155ContractCreationReceipt.contractAddress, + ); + mockTokenMetadataResponse(); }); @@ -557,6 +635,22 @@ describe("Ethereum persistence plugin tests", () => { expect(token.uri).toBeTruthy(); }); }); + + test("Synchronization of ERC1155 finds all issued tokens", async () => { + expect(erc1155ContractCreationReceipt.contractAddress).toBeTruthy(); + await persistence.refreshMonitoredTokens(); + await mintErc1155Token(constTestAcc.address, 1, 1, ""); + await mintErc1155Token(constTestAcc.address, 2, 1, ""); + await mintErc1155Token(constTestAcc.address, 3, 1, ""); + log.debug("Minting test ERC1155 tokens done."); + const upsertCalls = dbClientInstance.upsertTokenERC1155.mock.calls; + expect(upsertCalls.length).toBe(3); + upsertCalls.forEach((callArgs: any[]) => { + const token = callArgs[0]; + expect([1, 2, 3]).toContain(token.token_id); + expect(token.account_address).toBeTruthy(); + }); + }); }); ////////////////////////////////// diff --git a/packages/cactus-plugin-persistence-ethereum/src/test/typescript/integration/persistence-ethereum-postgresql-db-client.test.ts b/packages/cactus-plugin-persistence-ethereum/src/test/typescript/integration/persistence-ethereum-postgresql-db-client.test.ts index 541ef62604..3aba7920ed 100644 --- a/packages/cactus-plugin-persistence-ethereum/src/test/typescript/integration/persistence-ethereum-postgresql-db-client.test.ts +++ b/packages/cactus-plugin-persistence-ethereum/src/test/typescript/integration/persistence-ethereum-postgresql-db-client.test.ts @@ -125,9 +125,12 @@ describe("Ethereum persistence PostgreSQL PostgresDatabaseClient tests", () => { expect(tableNames.sort()).toEqual( [ "block", + "erc1155_token_history_view", "token_metadata_erc20", "token_metadata_erc721", + "token_erc1155", "token_erc721", + "token_metadata_erc1155", "token_transfer", "transaction", "erc20_token_history_view",