mirror of
https://github.com/getwax/wax.git
synced 2026-01-09 15:18:02 -05:00
clean
This commit is contained in:
@@ -1,17 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
/* eslint-disable @typescript-eslint/comma-dangle */
|
||||
/* eslint-disable prettier/prettier */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { expect } from "chai";
|
||||
import {
|
||||
BytesLike,
|
||||
JsonRpcProvider,
|
||||
NonceManager,
|
||||
Signer,
|
||||
ethers,
|
||||
} from "ethers";
|
||||
import { JsonRpcProvider, NonceManager, Signer, ethers } from "ethers";
|
||||
import DeterministicDeployer from "../../lib-ts/deterministic-deployer/DeterministicDeployer";
|
||||
import {
|
||||
SafeAnonAadhaarFactory__factory,
|
||||
@@ -31,23 +19,30 @@ import {
|
||||
init,
|
||||
generateArgs,
|
||||
prove,
|
||||
verify,
|
||||
artifactUrls,
|
||||
packGroth16Proof,
|
||||
ArtifactsOrigin,
|
||||
} from "@anon-aadhaar/core";
|
||||
import { testQRData } from "./utils/assets/dataInput.json";
|
||||
import fs from "fs";
|
||||
import { getUserOpHash } from "./utils/userOpUtils";
|
||||
import { copmuteUserNullifier } from "./utils/computeNullifier";
|
||||
import { getUserOpHash } from "@account-abstraction/utils";
|
||||
import { testQRData } from "./utils/assets/dataInput.json";
|
||||
|
||||
export const testPublicKeyHash =
|
||||
"15134874015316324267425466444584014077184337590635665158241104437045239495873";
|
||||
const oneEther = ethers.parseEther("1");
|
||||
// what is nullifier seed: https://anon-aadhaar-documentation.vercel.app/docs/nullifiers
|
||||
/*
|
||||
|
||||
This uses test Aadhaar QR code along with test Indian government's public key and certificate.
|
||||
|
||||
*/
|
||||
|
||||
// Nullifier seed: https://anon-aadhaar-documentation.vercel.app/docs/nullifiers
|
||||
// using wallet address makes sense...?
|
||||
const nullifierSeed = 1234;
|
||||
|
||||
// test version of UIDAI public key
|
||||
// more info: https://anon-aadhaar-documentation.vercel.app/docs/how-does-it-work#1-extract-and-process-the-data-from-the-qr-code
|
||||
const testPublicKeyHash =
|
||||
"15134874015316324267425466444584014077184337590635665158241104437045239495873";
|
||||
|
||||
describe("SafeAnonAadhaarPlugin", () => {
|
||||
let bundlerProvider: JsonRpcProvider;
|
||||
let provider: JsonRpcProvider;
|
||||
@@ -72,26 +67,18 @@ describe("SafeAnonAadhaarPlugin", () => {
|
||||
safeSingleton,
|
||||
} = setup);
|
||||
|
||||
console.log("admin: ", await admin.getAddress());
|
||||
console.log("owner: ", await owner.getAddress());
|
||||
console.log("entryPointAddress: ", entryPointAddress);
|
||||
console.log("safeSingleton: ", await safeSingleton.getAddress());
|
||||
|
||||
const signer = await provider.getSigner();
|
||||
|
||||
// Deploy AnonAadhaarGroth16Verifier contract
|
||||
const anonAadhaarVerifier = await new Verifier__factory(signer).deploy();
|
||||
|
||||
console.log(
|
||||
"anonAadhaarVerifier: ",
|
||||
await anonAadhaarVerifier.getAddress()
|
||||
);
|
||||
|
||||
// Deploy AnonAadhaar contract
|
||||
const anonAadhaar = await new AnonAadhaar__factory(signer).deploy(
|
||||
await anonAadhaarVerifier.getAddress(),
|
||||
BigInt(testPublicKeyHash).toString()
|
||||
);
|
||||
|
||||
anonAadhaarAddress = await anonAadhaar.getAddress();
|
||||
console.log("anonAadhaarAddress: ", anonAadhaarAddress);
|
||||
|
||||
// load test certificate
|
||||
const certificateDirName = __dirname + "/utils/assets";
|
||||
@@ -99,6 +86,7 @@ describe("SafeAnonAadhaarPlugin", () => {
|
||||
.readFileSync(certificateDirName + "/testCertificate.pem")
|
||||
.toString();
|
||||
|
||||
// load files needed for proof generation and verification
|
||||
const anonAadhaarInitArgs: InitArgs = {
|
||||
wasmURL: artifactUrls.v2.wasm,
|
||||
zkeyURL: artifactUrls.v2.zkey,
|
||||
@@ -106,18 +94,20 @@ describe("SafeAnonAadhaarPlugin", () => {
|
||||
artifactsOrigin: ArtifactsOrigin.server,
|
||||
};
|
||||
|
||||
// pass initArgs
|
||||
await init(anonAadhaarInitArgs);
|
||||
});
|
||||
|
||||
it("should pass the ERC4337 validation", async () => {
|
||||
// Deploy SafeAnonAadhaarFactory contract
|
||||
const safeAnonAadhaarFactory = await deployer.connectOrDeploy(
|
||||
SafeAnonAadhaarFactory__factory,
|
||||
[]
|
||||
);
|
||||
|
||||
// get user_data_hash
|
||||
// get userDataHash out of nullifier seed and test QR data
|
||||
// userDataHash is an unique identifier that is specific to each user and stored the plugin contract
|
||||
const userDataHash = await copmuteUserNullifier(nullifierSeed, testQRData);
|
||||
console.log("userDataHash: ", userDataHash);
|
||||
|
||||
const createArgs = [
|
||||
safeSingleton,
|
||||
@@ -132,8 +122,6 @@ describe("SafeAnonAadhaarPlugin", () => {
|
||||
...createArgs
|
||||
);
|
||||
|
||||
console.log("accountAddress: ", accountAddress);
|
||||
|
||||
await receiptOf(safeAnonAadhaarFactory.create(...createArgs));
|
||||
|
||||
const safeAnonAadhaarPlugin = SafeAnonAadhaarPlugin__factory.connect(
|
||||
@@ -157,9 +145,7 @@ describe("SafeAnonAadhaarPlugin", () => {
|
||||
[recipient.address, transferAmount, "0x00"]
|
||||
);
|
||||
|
||||
console.log("userOpCallData: ", userOpCallData);
|
||||
|
||||
// get userOp
|
||||
// create User Operation
|
||||
const unsignedUserOperation = await createAnonAadhaarOperation(
|
||||
provider,
|
||||
accountAddress,
|
||||
@@ -174,9 +160,7 @@ describe("SafeAnonAadhaarPlugin", () => {
|
||||
Number((await provider.getNetwork()).chainId)
|
||||
);
|
||||
|
||||
console.log("userOpHash: ", userOpHash);
|
||||
|
||||
// prove
|
||||
// prove with userOpHash as signal
|
||||
const args = await generateArgs({
|
||||
qrData: testQRData,
|
||||
certificateFile: certificate,
|
||||
@@ -184,22 +168,12 @@ describe("SafeAnonAadhaarPlugin", () => {
|
||||
signal: userOpHash, // user op hash
|
||||
});
|
||||
|
||||
console.log("args: ", args);
|
||||
|
||||
// proving
|
||||
const anonAadhaarCore = await prove(args);
|
||||
const ret = await verify(anonAadhaarCore);
|
||||
console.log("ret: ", ret);
|
||||
const anonAadhaarProof = anonAadhaarCore.proof;
|
||||
const packedGroth16Proof = packGroth16Proof(anonAadhaarProof.groth16Proof);
|
||||
|
||||
console.log("anonAadhaarCore: ", anonAadhaarCore);
|
||||
|
||||
console.log("nullifierSeed: ", anonAadhaarCore.proof.nullifierSeed);
|
||||
console.log("nullifier: ", anonAadhaarProof.nullifier);
|
||||
console.log("nullifier bg: ", BigInt(anonAadhaarProof.nullifier));
|
||||
console.log("timestamp: ", Number(anonAadhaarCore?.proof.timestamp));
|
||||
|
||||
// encode signautre
|
||||
// encode proof data into userOpSignature
|
||||
const encoder = ethers.AbiCoder.defaultAbiCoder();
|
||||
const userOpSignature = encoder.encode(
|
||||
["uint", "uint", "uint", "uint[4]", "uint[8]"],
|
||||
@@ -217,8 +191,6 @@ describe("SafeAnonAadhaarPlugin", () => {
|
||||
]
|
||||
);
|
||||
|
||||
console.log("userOpSignature: ", userOpSignature);
|
||||
|
||||
// send userOp
|
||||
await sendUserOpWithAnonAadhaarSig(
|
||||
bundlerProvider,
|
||||
@@ -227,61 +199,8 @@ describe("SafeAnonAadhaarPlugin", () => {
|
||||
userOpSignature
|
||||
);
|
||||
|
||||
console.log("userOp sent");
|
||||
|
||||
expect(await provider.getBalance(recipient.address)).to.equal(oneEther);
|
||||
expect(await provider.getBalance(recipient.address)).to.equal(
|
||||
ethers.parseEther("1")
|
||||
);
|
||||
}).timeout(0);
|
||||
|
||||
// it("should not allow execTransaction from unrelated address", async () => {
|
||||
// const {
|
||||
// provider,
|
||||
// admin,
|
||||
// owner,
|
||||
// entryPointAddress,
|
||||
// deployer,
|
||||
// safeSingleton,
|
||||
// } = await setupTests();
|
||||
|
||||
// const safeAnonAadhaarFactory = await deployer.connectOrDeploy(
|
||||
// SafeAnonAadhaarFactory__factory,
|
||||
// []
|
||||
// );
|
||||
|
||||
// const createArgs = [
|
||||
// safeSingleton,
|
||||
// entryPointAddress,
|
||||
// await owner.getAddress(),
|
||||
// 0,
|
||||
// "0x", // _anonAadhaarAddr
|
||||
// "0x", // _userDataHash
|
||||
// ] satisfies Parameters<typeof safeAnonAadhaarFactory.create.staticCall>;
|
||||
|
||||
// const accountAddress = await safeAnonAadhaarFactory.create.staticCall(
|
||||
// ...createArgs
|
||||
// );
|
||||
|
||||
// await receiptOf(safeAnonAadhaarFactory.create(...createArgs));
|
||||
|
||||
// const unrelatedWallet = ethers.Wallet.createRandom(provider);
|
||||
|
||||
// await receiptOf(
|
||||
// admin.sendTransaction({
|
||||
// to: unrelatedWallet.address,
|
||||
// value: 100n * oneEther,
|
||||
// })
|
||||
// );
|
||||
|
||||
// const account = SafeAnonAadhaarPlugin__factory.connect(
|
||||
// accountAddress,
|
||||
// unrelatedWallet
|
||||
// );
|
||||
|
||||
// const recipient = ethers.Wallet.createRandom(provider);
|
||||
|
||||
// await expect(
|
||||
// receiptOf(account.execTransaction(recipient.address, oneEther, "0x"))
|
||||
// ).to.eventually.rejected;
|
||||
|
||||
// await expect(provider.getBalance(recipient)).to.eventually.equal(0n);
|
||||
// });
|
||||
});
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/comma-dangle */
|
||||
/* eslint-disable prettier/prettier */
|
||||
import { ethers, getBytes, NonceManager, Signer } from "ethers";
|
||||
import { AddressZero } from "@ethersproject/constants";
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {
|
||||
AbiCoder,
|
||||
BigNumberish,
|
||||
BytesLike,
|
||||
concat,
|
||||
hexlify,
|
||||
isHexString,
|
||||
keccak256,
|
||||
AbiCoder,
|
||||
BigNumberish,
|
||||
BytesLike,
|
||||
concat,
|
||||
hexlify,
|
||||
isHexString,
|
||||
keccak256,
|
||||
} from "ethers";
|
||||
import { PackedUserOperationStruct } from "../../../typechain-types/lib/account-abstraction/contracts/core/EntryPoint";
|
||||
|
||||
@@ -21,26 +21,26 @@ import { PackedUserOperationStruct } from "../../../typechain-types/lib/account-
|
||||
export type PackedUserOperation = PackedUserOperationStruct;
|
||||
|
||||
export type UserOperation = {
|
||||
sender: string;
|
||||
nonce: BigNumberish;
|
||||
factory?: string;
|
||||
factoryData?: BytesLike;
|
||||
callData: BytesLike;
|
||||
callGasLimit: BigNumberish;
|
||||
verificationGasLimit: BigNumberish;
|
||||
preVerificationGas: BigNumberish;
|
||||
maxFeePerGas: BigNumberish;
|
||||
maxPriorityFeePerGas: BigNumberish;
|
||||
paymaster?: string;
|
||||
paymasterVerificationGasLimit?: BigNumberish;
|
||||
paymasterPostOpGasLimit?: BigNumberish;
|
||||
paymasterData?: BytesLike;
|
||||
signature: BytesLike;
|
||||
sender: string;
|
||||
nonce: BigNumberish;
|
||||
factory?: string;
|
||||
factoryData?: BytesLike;
|
||||
callData: BytesLike;
|
||||
callGasLimit: BigNumberish;
|
||||
verificationGasLimit: BigNumberish;
|
||||
preVerificationGas: BigNumberish;
|
||||
maxFeePerGas: BigNumberish;
|
||||
maxPriorityFeePerGas: BigNumberish;
|
||||
paymaster?: string;
|
||||
paymasterVerificationGasLimit?: BigNumberish;
|
||||
paymasterPostOpGasLimit?: BigNumberish;
|
||||
paymasterData?: BytesLike;
|
||||
signature: BytesLike;
|
||||
};
|
||||
|
||||
export type FactoryParams = {
|
||||
factory: string;
|
||||
factoryData?: BytesLike;
|
||||
factory: string;
|
||||
factoryData?: BytesLike;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -53,17 +53,17 @@ export type FactoryParams = {
|
||||
* @param chainId
|
||||
*/
|
||||
export function getUserOpHash(
|
||||
op: UserOperation,
|
||||
entryPoint: string,
|
||||
chainId: number,
|
||||
op: UserOperation,
|
||||
entryPoint: string,
|
||||
chainId: number
|
||||
): string {
|
||||
const userOpHash = keccak256(encodeUserOp(op, true));
|
||||
const defaultAbiCoder = AbiCoder.defaultAbiCoder();
|
||||
const enc = defaultAbiCoder.encode(
|
||||
["bytes32", "address", "uint256"],
|
||||
[userOpHash, entryPoint, chainId],
|
||||
);
|
||||
return keccak256(enc);
|
||||
const userOpHash = keccak256(encodeUserOp(op, true));
|
||||
const defaultAbiCoder = AbiCoder.defaultAbiCoder();
|
||||
const enc = defaultAbiCoder.encode(
|
||||
["bytes32", "address", "uint256"],
|
||||
[userOpHash, entryPoint, chainId]
|
||||
);
|
||||
return keccak256(enc);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,122 +73,122 @@ export function getUserOpHash(
|
||||
* "false" to pack entire UserOp, for calculating the calldata cost of putting it on-chain.
|
||||
*/
|
||||
export function encodeUserOp(
|
||||
op1: PackedUserOperation | UserOperation,
|
||||
forSignature = true,
|
||||
op1: PackedUserOperation | UserOperation,
|
||||
forSignature = true
|
||||
): string {
|
||||
// if "op" is unpacked UserOperation, then pack it first, before we ABI-encode it.
|
||||
// if "op" is unpacked UserOperation, then pack it first, before we ABI-encode it.
|
||||
|
||||
let op: PackedUserOperation;
|
||||
if ("callGasLimit" in op1) {
|
||||
op = packUserOp(op1);
|
||||
} else {
|
||||
op = op1;
|
||||
}
|
||||
let op: PackedUserOperation;
|
||||
if ("callGasLimit" in op1) {
|
||||
op = packUserOp(op1);
|
||||
} else {
|
||||
op = op1;
|
||||
}
|
||||
|
||||
const defaultAbiCoder = AbiCoder.defaultAbiCoder();
|
||||
if (forSignature) {
|
||||
return defaultAbiCoder.encode(
|
||||
[
|
||||
"address",
|
||||
"uint256",
|
||||
"bytes32",
|
||||
"bytes32",
|
||||
"bytes32",
|
||||
"uint256",
|
||||
"bytes32",
|
||||
"bytes32",
|
||||
],
|
||||
[
|
||||
op.sender,
|
||||
op.nonce,
|
||||
keccak256(op.initCode),
|
||||
keccak256(op.callData),
|
||||
op.accountGasLimits,
|
||||
op.preVerificationGas,
|
||||
op.gasFees,
|
||||
keccak256(op.paymasterAndData),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
// for the purpose of calculating gas cost encode also signature (and no keccak of bytes)
|
||||
return defaultAbiCoder.encode(
|
||||
[
|
||||
"address",
|
||||
"uint256",
|
||||
"bytes",
|
||||
"bytes",
|
||||
"bytes32",
|
||||
"uint256",
|
||||
"bytes32",
|
||||
"bytes",
|
||||
"bytes",
|
||||
],
|
||||
[
|
||||
op.sender,
|
||||
op.nonce,
|
||||
op.initCode,
|
||||
op.callData,
|
||||
op.accountGasLimits,
|
||||
op.preVerificationGas,
|
||||
op.gasFees,
|
||||
op.paymasterAndData,
|
||||
op.signature,
|
||||
],
|
||||
);
|
||||
}
|
||||
const defaultAbiCoder = AbiCoder.defaultAbiCoder();
|
||||
if (forSignature) {
|
||||
return defaultAbiCoder.encode(
|
||||
[
|
||||
"address",
|
||||
"uint256",
|
||||
"bytes32",
|
||||
"bytes32",
|
||||
"bytes32",
|
||||
"uint256",
|
||||
"bytes32",
|
||||
"bytes32",
|
||||
],
|
||||
[
|
||||
op.sender,
|
||||
op.nonce,
|
||||
keccak256(op.initCode),
|
||||
keccak256(op.callData),
|
||||
op.accountGasLimits,
|
||||
op.preVerificationGas,
|
||||
op.gasFees,
|
||||
keccak256(op.paymasterAndData),
|
||||
]
|
||||
);
|
||||
} else {
|
||||
// for the purpose of calculating gas cost encode also signature (and no keccak of bytes)
|
||||
return defaultAbiCoder.encode(
|
||||
[
|
||||
"address",
|
||||
"uint256",
|
||||
"bytes",
|
||||
"bytes",
|
||||
"bytes32",
|
||||
"uint256",
|
||||
"bytes32",
|
||||
"bytes",
|
||||
"bytes",
|
||||
],
|
||||
[
|
||||
op.sender,
|
||||
op.nonce,
|
||||
op.initCode,
|
||||
op.callData,
|
||||
op.accountGasLimits,
|
||||
op.preVerificationGas,
|
||||
op.gasFees,
|
||||
op.paymasterAndData,
|
||||
op.signature,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function packUserOp(op: UserOperation): PackedUserOperation {
|
||||
let paymasterAndData: BytesLike;
|
||||
if (op.paymaster == null) {
|
||||
paymasterAndData = "0x";
|
||||
} else {
|
||||
if (
|
||||
op.paymasterVerificationGasLimit == null ||
|
||||
op.paymasterPostOpGasLimit == null
|
||||
) {
|
||||
throw new Error("paymaster with no gas limits");
|
||||
}
|
||||
paymasterAndData = packPaymasterData(
|
||||
op.paymaster,
|
||||
op.paymasterVerificationGasLimit,
|
||||
op.paymasterPostOpGasLimit,
|
||||
op.paymasterData,
|
||||
);
|
||||
}
|
||||
let paymasterAndData: BytesLike;
|
||||
if (op.paymaster == null) {
|
||||
paymasterAndData = "0x";
|
||||
} else {
|
||||
if (
|
||||
op.paymasterVerificationGasLimit == null ||
|
||||
op.paymasterPostOpGasLimit == null
|
||||
) {
|
||||
throw new Error("paymaster with no gas limits");
|
||||
}
|
||||
paymasterAndData = packPaymasterData(
|
||||
op.paymaster,
|
||||
op.paymasterVerificationGasLimit,
|
||||
op.paymasterPostOpGasLimit,
|
||||
op.paymasterData
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
sender: op.sender,
|
||||
nonce: "0x" + BigInt(op.nonce).toString(16),
|
||||
initCode:
|
||||
op.factory == null ? "0x" : concat([op.factory, op.factoryData ?? ""]),
|
||||
callData: op.callData,
|
||||
accountGasLimits: packUint(op.verificationGasLimit, op.callGasLimit),
|
||||
preVerificationGas: "0x" + BigInt(op.preVerificationGas).toString(16),
|
||||
gasFees: packUint(op.maxPriorityFeePerGas, op.maxFeePerGas),
|
||||
paymasterAndData,
|
||||
signature: op.signature,
|
||||
};
|
||||
return {
|
||||
sender: op.sender,
|
||||
nonce: "0x" + BigInt(op.nonce).toString(16),
|
||||
initCode:
|
||||
op.factory == null ? "0x" : concat([op.factory, op.factoryData ?? ""]),
|
||||
callData: op.callData,
|
||||
accountGasLimits: packUint(op.verificationGasLimit, op.callGasLimit),
|
||||
preVerificationGas: "0x" + BigInt(op.preVerificationGas).toString(16),
|
||||
gasFees: packUint(op.maxPriorityFeePerGas, op.maxFeePerGas),
|
||||
paymasterAndData,
|
||||
signature: op.signature,
|
||||
};
|
||||
}
|
||||
|
||||
export function packPaymasterData(
|
||||
paymaster: string,
|
||||
paymasterVerificationGasLimit: BigNumberish,
|
||||
postOpGasLimit: BigNumberish,
|
||||
paymasterData?: BytesLike,
|
||||
paymaster: string,
|
||||
paymasterVerificationGasLimit: BigNumberish,
|
||||
postOpGasLimit: BigNumberish,
|
||||
paymasterData?: BytesLike
|
||||
): BytesLike {
|
||||
return concat([
|
||||
paymaster,
|
||||
packUint(paymasterVerificationGasLimit, postOpGasLimit),
|
||||
paymasterData ?? "0x",
|
||||
]);
|
||||
return concat([
|
||||
paymaster,
|
||||
packUint(paymasterVerificationGasLimit, postOpGasLimit),
|
||||
paymasterData ?? "0x",
|
||||
]);
|
||||
}
|
||||
|
||||
export function packUint(high128: BigNumberish, low128: BigNumberish): string {
|
||||
high128 = BigInt(high128);
|
||||
low128 = BigInt(low128);
|
||||
const packed = "0x" + ((high128 << 128n) + low128).toString(16);
|
||||
return hexZeroPad(packed, 32);
|
||||
high128 = BigInt(high128);
|
||||
low128 = BigInt(low128);
|
||||
const packed = "0x" + ((high128 << 128n) + low128).toString(16);
|
||||
return hexZeroPad(packed, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,19 +196,19 @@ export function packUint(high128: BigNumberish, low128: BigNumberish): string {
|
||||
* in a way the bundler doesn't expect and an error is thrown
|
||||
*/
|
||||
export function hexZeroPad(value: BytesLike, length: number): string {
|
||||
if (typeof value !== "string") {
|
||||
value = hexlify(value);
|
||||
} else if (!isHexString(value)) {
|
||||
console.log("invalid hex string", "value", value);
|
||||
}
|
||||
if (typeof value !== "string") {
|
||||
value = hexlify(value);
|
||||
} else if (!isHexString(value)) {
|
||||
console.log("invalid hex string", "value", value);
|
||||
}
|
||||
|
||||
if (value.length > 2 * length + 2) {
|
||||
console.log("value out of range", "value", arguments[1]);
|
||||
}
|
||||
if (value.length > 2 * length + 2) {
|
||||
console.log("value out of range", "value", arguments[1]);
|
||||
}
|
||||
|
||||
while (value.length < 2 * length + 2) {
|
||||
value = "0x0" + value.substring(2);
|
||||
}
|
||||
while (value.length < 2 * length + 2) {
|
||||
value = "0x0" + value.substring(2);
|
||||
}
|
||||
|
||||
return value;
|
||||
return value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user