|
| 1 | +import Keyring from '@polkadot/keyring'; |
| 2 | +import { cryptoWaitReady } from '@polkadot/util-crypto'; |
| 3 | +import { DedotClient, ISubstrateClient, LegacyClient, WsProvider } from 'dedot'; |
| 4 | +import { SubstrateApi } from 'dedot/chaintypes'; |
| 5 | +import { Contract, ContractDeployer, ContractMetadata, parseRawMetadata } from 'dedot/contracts'; |
| 6 | +import { RpcVersion } from 'dedot/types'; |
| 7 | +import { assert, isHex, isNumber, stringToHex } from 'dedot/utils'; |
| 8 | +import * as flipperV4Raw from '../flipper_v4.json'; |
| 9 | +import * as flipperV5Raw from '../flipper_v5.json'; |
| 10 | +import { FlipperContractApi } from './contracts/flipper'; |
| 11 | + |
| 12 | +export const run = async (_nodeName: any, networkInfo: any) => { |
| 13 | + await cryptoWaitReady(); |
| 14 | + |
| 15 | + const alicePair = new Keyring({ type: 'sr25519' }).addFromUri('//Alice'); |
| 16 | + const { wsUri } = networkInfo.nodesByName['collator-1']; |
| 17 | + |
| 18 | + const caller = alicePair.address; |
| 19 | + const flipperV4 = parseRawMetadata(JSON.stringify(flipperV4Raw)); |
| 20 | + const flipperV5 = parseRawMetadata(JSON.stringify(flipperV5Raw)); |
| 21 | + |
| 22 | + // Test with LegacyClient |
| 23 | + console.log('Testing contract chaining methods with LegacyClient'); |
| 24 | + const apiLegacy = await LegacyClient.new(new WsProvider(wsUri)); |
| 25 | + await testContractChainingMethods(apiLegacy, flipperV4, alicePair, caller); |
| 26 | + |
| 27 | + // Test with DedotClient |
| 28 | + console.log('Testing contract chaining methods with DedotClient'); |
| 29 | + const apiV2 = await DedotClient.new(new WsProvider(wsUri)); |
| 30 | + await testContractChainingMethods(apiV2, flipperV5, alicePair, caller); |
| 31 | +}; |
| 32 | + |
| 33 | +async function testContractChainingMethods( |
| 34 | + api: ISubstrateClient<SubstrateApi[RpcVersion]>, |
| 35 | + flipper: ContractMetadata, |
| 36 | + alicePair: any, |
| 37 | + caller: string |
| 38 | +) { |
| 39 | + const wasm = flipper.source.wasm!; |
| 40 | + const deployer = new ContractDeployer<FlipperContractApi>(api, flipper, wasm, { defaultCaller: caller }); |
| 41 | + |
| 42 | + // Avoid using the same salt with previous tests |
| 43 | + const timestamp = await api.query.timestamp.now(); |
| 44 | + const salt = stringToHex(`${api.rpcVersion}_${timestamp}_chaining_test`); |
| 45 | + |
| 46 | + // Dry-run to estimate gas fee |
| 47 | + const { |
| 48 | + raw: { gasRequired }, |
| 49 | + } = await deployer.query.new(true, { |
| 50 | + salt, |
| 51 | + }); |
| 52 | + |
| 53 | + console.log(`[${api.rpcVersion}] Testing untilBestChainBlockIncluded with contract deployment`); |
| 54 | + |
| 55 | + // Test untilBestChainBlockIncluded with contract deployment |
| 56 | + const bestChainResult = await deployer.tx |
| 57 | + .new(true, { gasLimit: gasRequired, salt }) |
| 58 | + .signAndSend(alicePair) |
| 59 | + .untilBestChainBlockIncluded(); |
| 60 | + |
| 61 | + // Verify the result contains the expected status |
| 62 | + assert(bestChainResult.status.type === 'BestChainBlockIncluded', 'Status should be BestChainBlockIncluded'); |
| 63 | + assert(isHex(bestChainResult.status.value.blockHash), 'Block hash should be hex'); |
| 64 | + assert(isNumber(bestChainResult.status.value.blockNumber), 'Block number should be number'); |
| 65 | + assert(isNumber(bestChainResult.status.value.txIndex), 'Tx index should be number'); |
| 66 | + |
| 67 | + // Verify the contract was deployed successfully |
| 68 | + const instantiatedEvent = api.events.contracts.Instantiated.find(bestChainResult.events); |
| 69 | + assert(instantiatedEvent, 'Event Contracts.Instantiated should be available'); |
| 70 | + |
| 71 | + const contractAddress = instantiatedEvent.palletEvent.data.contract.address(); |
| 72 | + console.log(`[${api.rpcVersion}] Deployed contract address`, contractAddress); |
| 73 | + |
| 74 | + const contract = new Contract<FlipperContractApi>(api, flipper, contractAddress, { defaultCaller: caller }); |
| 75 | + |
| 76 | + // Get initial state |
| 77 | + const { data: initialState } = await contract.query.get(); |
| 78 | + console.log(`[${api.rpcVersion}] Initial value:`, initialState); |
| 79 | + |
| 80 | + // Dry-run to estimate gas fee for flip |
| 81 | + const { raw } = await contract.query.flip(); |
| 82 | + |
| 83 | + console.log(`[${api.rpcVersion}] Testing untilFinalized with contract method call`); |
| 84 | + |
| 85 | + // Test untilFinalized with contract method call |
| 86 | + const finalizedResult = await contract.tx |
| 87 | + .flip({ gasLimit: raw.gasRequired }) |
| 88 | + .signAndSend(alicePair) |
| 89 | + .untilFinalized(); |
| 90 | + |
| 91 | + // Verify the result contains the expected status |
| 92 | + assert(finalizedResult.status.type === 'Finalized', 'Status should be Finalized'); |
| 93 | + assert(isHex(finalizedResult.status.value.blockHash), 'Block hash should be hex'); |
| 94 | + assert(isNumber(finalizedResult.status.value.blockNumber), 'Block number should be number'); |
| 95 | + assert(isNumber(finalizedResult.status.value.txIndex), 'Tx index should be number'); |
| 96 | + |
| 97 | + // Verify the flip was successful |
| 98 | + const flippedEvent = contract.events.Flipped.find(finalizedResult.events); |
| 99 | + assert(flippedEvent, 'Flipped event should be emitted'); |
| 100 | + assert(flippedEvent.data.new === false, 'New value should be false'); |
| 101 | + assert(flippedEvent.data.old === true, 'Old value should be true'); |
| 102 | + |
| 103 | + // Verify the state was changed |
| 104 | + const { data: newState } = await contract.query.get(); |
| 105 | + console.log(`[${api.rpcVersion}] New value:`, newState); |
| 106 | + assert(initialState !== newState, 'State should be changed'); |
| 107 | + |
| 108 | + console.log(`[${api.rpcVersion}] Testing order of events with contract method call`); |
| 109 | + |
| 110 | + // Test the order of events |
| 111 | + await testContractEventOrder(contract, alicePair, raw.gasRequired); |
| 112 | + |
| 113 | + console.log(`[${api.rpcVersion}] Testing combined promises with contract method call`); |
| 114 | + |
| 115 | + // Test using both promises together |
| 116 | + await testContractCombinedPromises(contract, alicePair, raw.gasRequired); |
| 117 | +} |
| 118 | + |
| 119 | +async function testContractEventOrder( |
| 120 | + contract: Contract<FlipperContractApi>, |
| 121 | + alicePair: any, |
| 122 | + gasLimit: any |
| 123 | +) { |
| 124 | + // Track the order of events |
| 125 | + let bestChainBlockIncludedReceived = false; |
| 126 | + let finalizedReceived = false; |
| 127 | + let bestChainBlockIncludedTime = 0; |
| 128 | + let finalizedTime = 0; |
| 129 | + |
| 130 | + // Send the transaction and track status updates |
| 131 | + await new Promise<void>((resolve) => { |
| 132 | + contract.tx |
| 133 | + .flip({ gasLimit }) |
| 134 | + .signAndSend(alicePair, ({ status }: any) => { |
| 135 | + if (status.type === 'BestChainBlockIncluded') { |
| 136 | + bestChainBlockIncludedReceived = true; |
| 137 | + bestChainBlockIncludedTime = Date.now(); |
| 138 | + console.log('Received BestChainBlockIncluded status at:', bestChainBlockIncludedTime); |
| 139 | + } else if (status.type === 'Finalized') { |
| 140 | + finalizedReceived = true; |
| 141 | + finalizedTime = Date.now(); |
| 142 | + console.log('Received Finalized status at:', finalizedTime); |
| 143 | + resolve(); |
| 144 | + } |
| 145 | + }); |
| 146 | + }); |
| 147 | + |
| 148 | + // Verify both statuses were received and in the correct order |
| 149 | + assert(bestChainBlockIncludedReceived, 'BestChainBlockIncluded status should be received'); |
| 150 | + assert(finalizedReceived, 'Finalized status should be received'); |
| 151 | + assert(bestChainBlockIncludedTime < finalizedTime, 'BestChainBlockIncluded should be received before Finalized'); |
| 152 | +} |
| 153 | + |
| 154 | +async function testContractCombinedPromises( |
| 155 | + contract: Contract<FlipperContractApi>, |
| 156 | + alicePair: any, |
| 157 | + gasLimit: any |
| 158 | +) { |
| 159 | + // Create two promises using both chaining methods |
| 160 | + const signedTx = contract.tx |
| 161 | + .flip({ gasLimit }) |
| 162 | + .signAndSend(alicePair); |
| 163 | + |
| 164 | + const bestChainPromise = signedTx.untilBestChainBlockIncluded(); |
| 165 | + const finalizedPromise = signedTx.untilFinalized(); |
| 166 | + |
| 167 | + // Wait for both promises to resolve |
| 168 | + const [bestChainResult, finalizedResult] = await Promise.all([bestChainPromise, finalizedPromise]); |
| 169 | + |
| 170 | + // Verify the results |
| 171 | + assert(bestChainResult.status.type === 'BestChainBlockIncluded', 'First result status should be BestChainBlockIncluded'); |
| 172 | + assert(finalizedResult.status.type === 'Finalized', 'Second result status should be Finalized'); |
| 173 | + |
| 174 | + // Verify both results refer to the same transaction |
| 175 | + assert(bestChainResult.txHash === finalizedResult.txHash, 'Both results should have the same txHash'); |
| 176 | + |
| 177 | + // Verify the flip was successful |
| 178 | + const flippedEvent = contract.events.Flipped.find(finalizedResult.events); |
| 179 | + assert(flippedEvent, 'Flipped event should be emitted'); |
| 180 | +} |
0 commit comments