|
3 | 3 |
|
4 | 4 | import { useNavigate } from 'react-router';
|
5 | 5 | import type { BlueprintSubmittableResult } from 'types';
|
6 |
| -import { useDatabase, useInstantiate } from 'ui/contexts'; |
| 6 | +import { useApi, useDatabase, useInstantiate } from 'ui/contexts'; |
| 7 | +import { ethers } from 'ethers'; |
| 8 | +import { ApiTypes } from '@polkadot/api/types'; |
| 9 | +import { stringToU8a } from '@polkadot/util'; |
| 10 | +import { keccak256 } from 'ethers'; |
| 11 | +import { decodeAddress, encodeAddress } from '@polkadot/keyring'; |
| 12 | + |
| 13 | +interface ExtendedBlueprintSubmittableResult<T extends ApiTypes> |
| 14 | + extends BlueprintSubmittableResult<T> { |
| 15 | + contractData?: { |
| 16 | + salt: Uint8Array; |
| 17 | + data: string; |
| 18 | + code: Uint8Array; |
| 19 | + originIsCaller?: boolean; |
| 20 | + }; |
| 21 | +} |
| 22 | + |
| 23 | +/** |
| 24 | + * TypeScript equivalent of H160 (20-byte Ethereum address) |
| 25 | + */ |
| 26 | +type Address = string; |
| 27 | + |
| 28 | +/** |
| 29 | + * Determine the address of a contract using CREATE semantics. |
| 30 | + * @param deployer The address of the deployer |
| 31 | + * @param nonce The nonce value |
| 32 | + * @returns The contract address |
| 33 | + */ |
| 34 | +export function create1(deployer: Address, nonce: bigint | number): Address { |
| 35 | + // Normalize the deployer address |
| 36 | + const deployerAddress = ethers.getAddress(deployer); |
| 37 | + |
| 38 | + // Create RLP encoding of the deployer address and nonce |
| 39 | + const encodedData = ethers.encodeRlp([deployerAddress, ethers.toBeHex(nonce)]); |
| 40 | + |
| 41 | + // Calculate keccak256 hash of the RLP encoded data |
| 42 | + const hash = ethers.keccak256(encodedData); |
| 43 | + |
| 44 | + // Take the last 20 bytes (40 hex chars + 0x prefix) |
| 45 | + return ethers.getAddress('0x' + hash.substring(26)); |
| 46 | +} |
| 47 | + |
| 48 | +// /** |
| 49 | +// * Determine the address of a contract using the CREATE2 semantics. |
| 50 | +// * @param deployer The address of the deployer |
| 51 | +// * @param code The initialization code |
| 52 | +// * @param inputData Additional input data |
| 53 | +// * @param salt A 32-byte salt value |
| 54 | +// * @returns The contract address |
| 55 | +// */ |
| 56 | +// export function create2( |
| 57 | +// deployer: Address, |
| 58 | +// code: Uint8Array | string, |
| 59 | +// inputData: Uint8Array | string, |
| 60 | +// salt: string |
| 61 | +// ): Address { |
| 62 | +// console.log("info"); |
| 63 | +// console.log(code); |
| 64 | +// console.log("data"); |
| 65 | +// console.log(inputData); |
| 66 | +// console.log("salt"); |
| 67 | +// console.log(salt); |
| 68 | +// |
| 69 | +// // Normalize inputs to Uint8Array |
| 70 | +// const codeBytes = typeof code === 'string' |
| 71 | +// ? stringToU8a(code) |
| 72 | +// : code; |
| 73 | +// |
| 74 | +// const inputDataBytes = typeof inputData === 'string' |
| 75 | +// ? stringToU8a(inputData) |
| 76 | +// : inputData; |
| 77 | +// |
| 78 | +// const normalizedSalt = stringToU8a(salt.substring(2)); |
| 79 | +// |
| 80 | +// // Concatenate code and input data |
| 81 | +// const initCode = new Uint8Array(codeBytes.length + inputDataBytes.length); |
| 82 | +// initCode.set(codeBytes); |
| 83 | +// initCode.set(inputDataBytes, codeBytes.length); |
| 84 | +// |
| 85 | +// // Calculate init code hash |
| 86 | +// const initCodeHash = ethers.keccak256(initCode); |
| 87 | +// |
| 88 | +// // Build the input buffer: 0xff + deployer + salt + initCodeHash |
| 89 | +// const buffer = ethers.concat([ |
| 90 | +// new Uint8Array([0xff]), |
| 91 | +// ethers.getBytes(deployer), |
| 92 | +// ethers.getBytes(normalizedSalt), |
| 93 | +// ethers.getBytes(initCodeHash) |
| 94 | +// ]); |
| 95 | +// |
| 96 | +// // Calculate keccak256 hash of the buffer |
| 97 | +// const hash = ethers.keccak256(buffer); |
| 98 | +// |
| 99 | +// // Take the last 20 bytes (40 hex chars + 0x prefix) |
| 100 | +// return ethers.getAddress("0x" + hash.substring(26)); |
| 101 | +// } |
| 102 | + |
| 103 | +// function create2(deployer: Address, code: Uint8Array, input_data: Uint8Array, salt: Uint8Array): Address { |
| 104 | +// const init_code = Uint8Array.from([...code, ...input_data]); |
| 105 | +// const init_code_hash = keccak256(init_code); |
| 106 | +// const bytes = new Uint8Array(85); |
| 107 | +// bytes[0] = 0xff; |
| 108 | +// bytes.set(stringToU8a(deployer.toString()), 1); |
| 109 | +// console.log(salt); |
| 110 | +// console.log(salt.length); |
| 111 | +// bytes.set(salt.slice(0, 32), 21); |
| 112 | +// console.log(init_code_hash.length); |
| 113 | +// bytes.set(stringToU8a(init_code_hash.substring(2)), 53); |
| 114 | +// const hash = stringToU8a(keccak256(bytes).substring(2)); |
| 115 | +// const addressBytes = hash.slice(12); |
| 116 | +// // const address = new (addressBytes); |
| 117 | +// console.log("addressBytes"); |
| 118 | +// console.log(addressBytes); |
| 119 | +// console.log(addressBytes.toString()); |
| 120 | +// return addressBytes.toString(); |
| 121 | +// } |
| 122 | + |
| 123 | +export function create2( |
| 124 | + deployer: string, |
| 125 | + code: Uint8Array, |
| 126 | + inputData: Uint8Array, |
| 127 | + salt: Uint8Array, |
| 128 | +): string { |
| 129 | + // if (salt.length !== 32) { |
| 130 | + // throw new Error('Salt must be 32 bytes'); |
| 131 | + // } |
| 132 | + |
| 133 | + const initCode = new Uint8Array([...code, ...inputData]); |
| 134 | + const initCodeHash = stringToU8a(keccak256(initCode)); |
| 135 | + |
| 136 | + const parts = new Uint8Array(1 + (20 + 32 + 32) * 2); // 0xff + deployer + salt + initCodeHash |
| 137 | + parts[0] = 0xff; |
| 138 | + parts.set(stringToU8a(deployer.slice(2)), 1); |
| 139 | + console.log('HERE'); |
| 140 | + console.log(initCodeHash); |
| 141 | + parts.set(salt.slice(2), 21); |
| 142 | + parts.set(initCodeHash.slice(2), 53); |
| 143 | + |
| 144 | + const hash = keccak256(parts); |
| 145 | + |
| 146 | + // Return last 20 bytes as 0x-prefixed hex string |
| 147 | + // return '0x' + Buffer.from(hash.slice(12, 42)).toString('hex'); |
| 148 | + console.log(hash); |
| 149 | + console.log(hash.toString()); |
| 150 | + return ethers.getAddress('0x' + hash.substring(26)); |
| 151 | +} |
| 152 | + |
| 153 | +/** |
| 154 | + * Converts an account ID to an Ethereum address (H160) |
| 155 | + * @param accountId The account ID bytes |
| 156 | + * @returns The Ethereum address |
| 157 | + */ |
| 158 | +export function toEthAddress(accountId: Uint8Array | string): string { |
| 159 | + // Convert string input to Uint8Array if needed |
| 160 | + const accountBytes = typeof accountId === 'string' ? stringToU8a(accountId) : accountId; |
| 161 | + |
| 162 | + // Create a 32-byte buffer and copy account bytes into it |
| 163 | + const accountBuffer = new Uint8Array(32); |
| 164 | + accountBuffer.set(accountBytes.slice(0, 32)); |
| 165 | + |
| 166 | + if (isEthDerived(accountBytes)) { |
| 167 | + // This was originally an eth address |
| 168 | + // We just strip the 0xEE suffix to get the original address |
| 169 | + return '0x' + Buffer.from(accountBuffer.slice(0, 20)).toString('hex'); |
| 170 | + } else { |
| 171 | + // This is an (ed|sr)25519 derived address |
| 172 | + // Avoid truncating the public key by hashing it first |
| 173 | + const accountHash = ethers.keccak256(accountBuffer); |
| 174 | + return ethers.getAddress('0x' + accountHash.slice(2 + 24, 2 + 24 + 40)); // Skip '0x' prefix, then skip 12 bytes, take 20 bytes |
| 175 | + } |
| 176 | +} |
| 177 | + |
| 178 | +/** |
| 179 | + * Determines if an account ID is derived from an Ethereum address |
| 180 | + * @param accountId The account ID bytes |
| 181 | + * @returns True if the account is derived from an Ethereum address |
| 182 | + */ |
| 183 | +function isEthDerived(accountId: Uint8Array): boolean { |
| 184 | + if (accountId.length >= 32) { |
| 185 | + return accountId[20] === 0xee && accountId[21] === 0xee; |
| 186 | + } |
| 187 | + return false; |
| 188 | +} |
7 | 189 |
|
8 | 190 | export function useNewContract() {
|
9 | 191 | const { db } = useDatabase();
|
10 | 192 | const navigate = useNavigate();
|
| 193 | + const instantiate = useInstantiate(); |
| 194 | + const { api } = useApi(); |
| 195 | + |
| 196 | + console.log('Instantiate', instantiate); |
| 197 | + |
11 | 198 | const {
|
12 | 199 | data: { accountId, name },
|
13 |
| - } = useInstantiate(); |
| 200 | + } = instantiate; |
| 201 | + |
| 202 | + async function getNonce() { |
| 203 | + try { |
| 204 | + const nonce = await api.call.accountNonceApi.accountNonce(accountId); |
| 205 | + return nonce.toNumber(); |
| 206 | + } catch (error) { |
| 207 | + console.error('Error fetching nonce:', error); |
| 208 | + return null; |
| 209 | + } |
| 210 | + } |
| 211 | + |
| 212 | + return async function ({ |
| 213 | + contract, |
| 214 | + contractData, |
| 215 | + }: ExtendedBlueprintSubmittableResult<'promise'>): Promise<void> { |
| 216 | + console.log('Processing contract submission'); |
| 217 | + console.log(contract); |
| 218 | + console.log(contractData); |
| 219 | + console.log(contractData?.code.toString()); |
14 | 220 |
|
15 |
| - return async function ({ contract }: BlueprintSubmittableResult<'promise'>): Promise<void> { |
16 | 221 | if (accountId && contract?.abi.json) {
|
| 222 | + // Calculate the expected contract address based on the Rust logic |
| 223 | + let calculatedAddress; |
| 224 | + |
| 225 | + if (contractData) { |
| 226 | + const { salt, code, data, originIsCaller = false } = contractData; |
| 227 | + const mappedAccount = toEthAddress(decodeAddress(accountId)); |
| 228 | + console.log('Mapped account address:', mappedAccount); |
| 229 | + console.log(mappedAccount); |
| 230 | + |
| 231 | + if (salt) { |
| 232 | + // Use CREATE2 if salt is provided |
| 233 | + calculatedAddress = create2(mappedAccount, code, data, salt); |
| 234 | + console.log('CREATE2 calculated address:', calculatedAddress); |
| 235 | + } else { |
| 236 | + // Use CREATE1 if no salt is provided |
| 237 | + const nonce = await getNonce(); |
| 238 | + |
| 239 | + if (nonce !== null) { |
| 240 | + const adjustedNonce = originIsCaller ? Math.max(0, nonce - 1) : nonce; |
| 241 | + calculatedAddress = create1(mappedAccount, BigInt(adjustedNonce)); |
| 242 | + console.log('CREATE1 calculated address with nonce:', adjustedNonce); |
| 243 | + console.log('Calculated address:', calculatedAddress); |
| 244 | + } |
| 245 | + } |
| 246 | + } |
| 247 | + |
17 | 248 | const codeHash = contract.abi.info.source.wasmHash.toHex();
|
| 249 | + |
18 | 250 | const document = {
|
19 | 251 | abi: contract.abi.json,
|
20 |
| - address: contract.address.toString(), |
| 252 | + address: calculatedAddress, |
21 | 253 | codeHash,
|
22 | 254 | date: new Date().toISOString(),
|
23 | 255 | name,
|
| 256 | + // Store the calculated address |
| 257 | + calculatedAddress: calculatedAddress || undefined, |
24 | 258 | };
|
25 | 259 |
|
26 | 260 | await Promise.all([
|
|
0 commit comments