From 2c11deccc48e30e85bae9dc1d464f9b44f2db4ad Mon Sep 17 00:00:00 2001 From: lbqds Date: Tue, 10 Sep 2024 14:29:09 +0800 Subject: [PATCH 1/4] Support testing contract private functions in integration tests --- .../web3/src/codec/contract-codec.test.ts | 2 +- packages/web3/src/contract/contract.ts | 89 ++++++++++++++----- 2 files changed, 69 insertions(+), 22 deletions(-) diff --git a/packages/web3/src/codec/contract-codec.test.ts b/packages/web3/src/codec/contract-codec.test.ts index 696be7a7d..4c17fde21 100644 --- a/packages/web3/src/codec/contract-codec.test.ts +++ b/packages/web3/src/codec/contract-codec.test.ts @@ -376,7 +376,7 @@ describe('Encode & decode contract', function () { expect(decodedContract.fieldLength).toEqual(getTypesLength(contract.fieldsSig.types)) decodedContract.methods.map((decodedMethod, index) => { - const decoded = contract.decodedMethods[index] + const decoded = contract.getDecodedMethod(index) const functionSig = contract.functions[index] expect(decodedMethod.isPublic).toEqual(decoded.isPublic) expect(decodedMethod.usePreapprovedAssets).toEqual(decoded.usePreapprovedAssets) diff --git a/packages/web3/src/contract/contract.ts b/packages/web3/src/contract/contract.ts index 16e05914e..5495fa18b 100644 --- a/packages/web3/src/contract/contract.ts +++ b/packages/web3/src/contract/contract.ts @@ -162,7 +162,7 @@ export abstract class Artifact { this.functions = functions } - abstract buildByteCodeToDeploy(initialFields: Fields, isDevnet: boolean): string + abstract buildByteCodeToDeploy(initialFields: Fields, isDevnet: boolean, exposePrivateFunctions: boolean): string async isDevnet(signer: SignerProvider): Promise { if (!signer.nodeProvider) { @@ -197,7 +197,10 @@ export class Contract extends Artifact { readonly bytecodeDebug: string readonly codeHashDebug: string - readonly decodedMethods: Method[] + readonly decodedDebugContract: contract.Contract + + private bytecodeForTesting: string | undefined + private codeHashForTesting: string | undefined constructor( version: string, @@ -230,19 +233,53 @@ export class Contract extends Artifact { this.bytecodeDebug = ralph.buildDebugBytecode(this.bytecode, this.bytecodeDebugPatch) this.codeHashDebug = codeHashDebug - this.decodedMethods = contract.contractCodec.decodeContract(hexToBinUnsafe(bytecode)).methods + this.decodedDebugContract = contract.contractCodec.decodeContract(hexToBinUnsafe(this.bytecodeDebug)) + this.bytecodeForTesting = undefined + this.codeHashForTesting = undefined + } + + getByteCodeForTesting(): string { + if (this.bytecodeForTesting !== undefined) return this.bytecodeForTesting + + if (this.publicFunctions().length == this.functions.length) { + this.bytecodeForTesting = this.bytecodeDebug + this.codeHashForTesting = this.codeHashDebug + return this.bytecodeForTesting + } + + const methods = this.decodedDebugContract.methods.map((method) => ({ ...method, isPublic: true })) + const bytecodeForTesting = contract.contractCodec.encodeContract({ + fieldLength: this.decodedDebugContract.fieldLength, + methods: methods + }) + const codeHashForTesting = blake.blake2b(bytecodeForTesting, undefined, 32) + this.bytecodeForTesting = binToHex(bytecodeForTesting) + this.codeHashForTesting = binToHex(codeHashForTesting) + return this.bytecodeForTesting + } + + hasCodeHash(hash: string): boolean { + return this.codeHash === hash || this.codeHashDebug === hash || this.codeHashForTesting === hash + } + + getDecodedMethod(methodIndex: number): Method { + return this.decodedDebugContract.methods[`${methodIndex}`] } publicFunctions(): FunctionSig[] { - return this.functions.filter((_, index) => this.decodedMethods[`${index}`].isPublic) + return this.functions.filter((_, index) => this.getDecodedMethod(index).isPublic) } usingPreapprovedAssetsFunctions(): FunctionSig[] { - return this.functions.filter((_, index) => this.decodedMethods[`${index}`].usePreapprovedAssets) + return this.functions.filter((_, index) => this.getDecodedMethod(index).usePreapprovedAssets) } usingAssetsInContractFunctions(): FunctionSig[] { - return this.functions.filter((_, index) => this.decodedMethods[`${index}`].useContractAssets) + return this.functions.filter((_, index) => this.getDecodedMethod(index).useContractAssets) + } + + isMethodUsePreapprovedAssets(methodIndex: number): boolean { + return this.getDecodedMethod(methodIndex).usePreapprovedAssets } // TODO: safely parse json @@ -526,11 +563,12 @@ export class Contract extends Artifact { async txParamsForDeployment

( signer: SignerProvider, - params: DeployContractParams

+ params: DeployContractParams

, + exposePrivateFunctions = false ): Promise { const isDevnet = await this.isDevnet(signer) const initialFields: Fields = params.initialFields ?? {} - const bytecode = this.buildByteCodeToDeploy(addStdIdToFields(this, initialFields), isDevnet) + const bytecode = this.buildByteCodeToDeploy(addStdIdToFields(this, initialFields), isDevnet, exposePrivateFunctions) const selectedAccount = await signer.getSelectedAccount() const signerParams: SignDeployContractTxParams = { signerAddress: selectedAccount.address, @@ -546,14 +584,15 @@ export class Contract extends Artifact { return signerParams } - buildByteCodeToDeploy(initialFields: Fields, isDevnet: boolean): string { + buildByteCodeToDeploy(initialFields: Fields, isDevnet: boolean, exposePrivateFunctions = false): string { try { - return ralph.buildContractByteCode( - isDevnet ? this.bytecodeDebug : this.bytecode, - initialFields, - this.fieldsSig, - this.structs - ) + const bytecode = + exposePrivateFunctions && isDevnet + ? this.getByteCodeForTesting() + : isDevnet + ? this.bytecodeDebug + : this.bytecode + return ralph.buildContractByteCode(bytecode, initialFields, this.fieldsSig, this.structs) } catch (error) { throw new Error(`Failed to build bytecode for contract ${this.name}, error: ${error}`) } @@ -998,11 +1037,19 @@ export abstract class ContractFactory): Promise> { - const signerParams = await this.contract.txParamsForDeployment(signer, { - ...deployParams, - initialFields: addStdIdToFields(this.contract, deployParams.initialFields) - }) + async deploy( + signer: SignerProvider, + deployParams: DeployContractParams, + exposePrivateFunctions = false + ): Promise> { + const signerParams = await this.contract.txParamsForDeployment( + signer, + { + ...deployParams, + initialFields: addStdIdToFields(this.contract, deployParams.initialFields) + }, + exposePrivateFunctions + ) const result = await signer.signAndSubmitDeployContractTx(signerParams) return { ...result, @@ -1687,7 +1734,7 @@ export async function signExecuteMethod { const methodIndex = contract.contract.getMethodIndex(methodName) const functionSig = contract.contract.functions[methodIndex] - const methodUsePreapprovedAssets = contract.contract.decodedMethods[methodIndex].usePreapprovedAssets + const methodUsePreapprovedAssets = contract.contract.isMethodUsePreapprovedAssets(methodIndex) const bytecodeTemplate = getBytecodeTemplate( methodIndex, methodUsePreapprovedAssets, From d62442e85bf5e502548da8b5d1929bbba9d00833 Mon Sep 17 00:00:00 2001 From: lbqds Date: Tue, 10 Sep 2024 14:30:07 +0800 Subject: [PATCH 2/4] Generate code for contract private functions --- artifacts/ts/Add.ts | 18 ++++++++++++++ artifacts/ts/MetaData.ts | 48 +++++++++++++++++++++++++++++++++++++ artifacts/ts/contracts.ts | 5 +--- packages/cli/src/codegen.ts | 13 +++++----- 4 files changed, 73 insertions(+), 11 deletions(-) diff --git a/artifacts/ts/Add.ts b/artifacts/ts/Add.ts index 71d7e412a..762d6b0a7 100644 --- a/artifacts/ts/Add.ts +++ b/artifacts/ts/Add.ts @@ -69,6 +69,10 @@ export namespace AddTypes { }>; result: CallContractResult<[bigint, bigint]>; }; + addPrivate: { + params: CallContractParams<{ array: [bigint, bigint] }>; + result: CallContractResult<[bigint, bigint]>; + }; createSubContract: { params: CallContractParams<{ a: bigint; @@ -122,6 +126,10 @@ export namespace AddTypes { }>; result: SignExecuteScriptTxResult; }; + addPrivate: { + params: SignExecuteContractMethodParams<{ array: [bigint, bigint] }>; + result: SignExecuteScriptTxResult; + }; createSubContract: { params: SignExecuteContractMethodParams<{ a: bigint; @@ -316,6 +324,11 @@ export class AddInstance extends ContractInstance { ): Promise> => { return callMethod(Add, this, "add2", params, getContractByCodeHash); }, + addPrivate: async ( + params: AddTypes.CallMethodParams<"addPrivate"> + ): Promise> => { + return callMethod(Add, this, "addPrivate", params, getContractByCodeHash); + }, createSubContract: async ( params: AddTypes.CallMethodParams<"createSubContract"> ): Promise> => { @@ -356,6 +369,11 @@ export class AddInstance extends ContractInstance { ): Promise> => { return signExecuteMethod(Add, this, "add2", params); }, + addPrivate: async ( + params: AddTypes.SignExecuteMethodParams<"addPrivate"> + ): Promise> => { + return signExecuteMethod(Add, this, "addPrivate", params); + }, createSubContract: async ( params: AddTypes.SignExecuteMethodParams<"createSubContract"> ): Promise> => { diff --git a/artifacts/ts/MetaData.ts b/artifacts/ts/MetaData.ts index aa4076eab..715a1d152 100644 --- a/artifacts/ts/MetaData.ts +++ b/artifacts/ts/MetaData.ts @@ -51,6 +51,14 @@ export namespace MetaDataTypes { params: Omit, "args">; result: CallContractResult; }; + bar: { + params: Omit, "args">; + result: CallContractResult; + }; + baz: { + params: Omit, "args">; + result: CallContractResult; + }; } export type CallMethodParams = CallMethodTable[T]["params"]; @@ -73,6 +81,14 @@ export namespace MetaDataTypes { params: Omit, "args">; result: SignExecuteScriptTxResult; }; + bar: { + params: Omit, "args">; + result: SignExecuteScriptTxResult; + }; + baz: { + params: Omit, "args">; + result: SignExecuteScriptTxResult; + }; } export type SignExecuteMethodParams = SignExecuteMethodTable[T]["params"]; @@ -164,6 +180,28 @@ export class MetaDataInstance extends ContractInstance { getContractByCodeHash ); }, + bar: async ( + params?: MetaDataTypes.CallMethodParams<"bar"> + ): Promise> => { + return callMethod( + MetaData, + this, + "bar", + params === undefined ? {} : params, + getContractByCodeHash + ); + }, + baz: async ( + params?: MetaDataTypes.CallMethodParams<"baz"> + ): Promise> => { + return callMethod( + MetaData, + this, + "baz", + params === undefined ? {} : params, + getContractByCodeHash + ); + }, }; transact = { @@ -172,5 +210,15 @@ export class MetaDataInstance extends ContractInstance { ): Promise> => { return signExecuteMethod(MetaData, this, "foo", params); }, + bar: async ( + params: MetaDataTypes.SignExecuteMethodParams<"bar"> + ): Promise> => { + return signExecuteMethod(MetaData, this, "bar", params); + }, + baz: async ( + params: MetaDataTypes.SignExecuteMethodParams<"baz"> + ): Promise> => { + return signExecuteMethod(MetaData, this, "baz", params); + }, }; } diff --git a/artifacts/ts/contracts.ts b/artifacts/ts/contracts.ts index 1a5d25d07..8847838e9 100644 --- a/artifacts/ts/contracts.ts +++ b/artifacts/ts/contracts.ts @@ -64,10 +64,7 @@ export function getContractByCodeHash(codeHash: string): Contract { WrongNFTTest, ]; } - const c = contracts.find( - (c) => - c.contract.codeHash === codeHash || c.contract.codeHashDebug === codeHash - ); + const c = contracts.find((c) => c.contract.hasCodeHash(codeHash)); if (c === undefined) { throw new Error("Unknown code with code hash: " + codeHash); } diff --git a/packages/cli/src/codegen.ts b/packages/cli/src/codegen.ts index cf5648fd1..fb98781b3 100644 --- a/packages/cli/src/codegen.ts +++ b/packages/cli/src/codegen.ts @@ -111,7 +111,7 @@ function genCallMethod(contractName: string, functionSig: FunctionSig): string { } function genCallMethods(contract: Contract): string { - const functions = contract.publicFunctions() + const functions = contract.functions if (functions.length === 0) { return '' } @@ -123,7 +123,7 @@ function genCallMethods(contract: Contract): string { } function genTxCallMethods(contract: Contract): string { - const functions = contract.publicFunctions() + const functions = contract.functions if (functions.length === 0) { return '' } @@ -379,7 +379,7 @@ function genTestMethods(contract: Contract): string { } function genCallMethodTypes(contract: Contract): string { - const entities = contract.publicFunctions().map((functionSig) => { + const entities = contract.functions.map((functionSig) => { const funcHasArgs = functionSig.paramNames.length > 0 const params = funcHasArgs ? `CallContractParams<{${formatParameters({ @@ -416,7 +416,7 @@ function genCallMethodTypes(contract: Contract): string { } function genSignExecuteMethodTypes(contract: Contract): string { - const entities = contract.publicFunctions().map((functionSig) => { + const entities = contract.functions.map((functionSig) => { const funcHasArgs = functionSig.paramNames.length > 0 const params = funcHasArgs ? `SignExecuteContractMethodParams<{${formatParameters({ @@ -445,8 +445,7 @@ function genSignExecuteMethodTypes(contract: Contract): string { function genMulticall(contract: Contract): string { const types = contractTypes(contract.name) - const supportMulticall = - contract.publicFunctions().filter((functionSig) => functionSig.returnTypes.length > 0).length > 0 + const supportMulticall = contract.functions.filter((functionSig) => functionSig.returnTypes.length > 0).length > 0 return supportMulticall ? ` async multicall( @@ -624,7 +623,7 @@ function genContractByCodeHash(outDir: string, contractNames: string[]) { if (contracts === undefined) { contracts = [${contracts}] } - const c = contracts.find((c) => c.contract.codeHash === codeHash || c.contract.codeHashDebug === codeHash) + const c = contracts.find((c) => c.contract.hasCodeHash(codeHash)) if (c === undefined) { throw new Error("Unknown code with code hash: " + codeHash) } From 6ee171da0811c7c38a21c97a59c7ff2dfe2d23cb Mon Sep 17 00:00:00 2001 From: lbqds Date: Tue, 10 Sep 2024 14:30:49 +0800 Subject: [PATCH 3/4] Add more tests --- test/contract.test.ts | 131 +++++++++++++++++++++++++++++++----------- 1 file changed, 99 insertions(+), 32 deletions(-) diff --git a/test/contract.test.ts b/test/contract.test.ts index e1810dd65..e24429f6b 100644 --- a/test/contract.test.ts +++ b/test/contract.test.ts @@ -42,7 +42,14 @@ import { MINIMAL_CONTRACT_DEPOSIT } from '../packages/web3' import { Contract, Script, getContractIdFromUnsignedTx } from '../packages/web3' -import { expectAssertionError, testAddress, randomContractAddress, getSigner, mintToken } from '../packages/web3-test' +import { + expectAssertionError, + testAddress, + randomContractAddress, + getSigner, + mintToken, + randomContractId +} from '../packages/web3-test' import { PrivateKeyWallet } from '@alephium/web3-wallet' import { Greeter, GreeterTypes } from '../artifacts/ts/Greeter' import { @@ -73,6 +80,7 @@ describe('contract', function () { let signer: PrivateKeyWallet let signerAccount: Account let signerGroup: number + let exposePrivateFunctions: boolean beforeAll(async () => { web3.setCurrentNodeProvider('http://127.0.0.1:22973', undefined, fetch) @@ -80,6 +88,7 @@ describe('contract', function () { signer = await getSigner() signerAccount = signer.account signerGroup = signerAccount.group + exposePrivateFunctions = Math.random() < 0.5 expect(signerGroup).toEqual(groupOfAddress(testAddress)) }) @@ -93,11 +102,15 @@ describe('contract', function () { it('should get contract id from tx id', async () => { const nodeProvider = web3.getCurrentNodeProvider() - const deployResult0 = await Sub.deploy(signer, { initialFields: { result: 0n } }) + const deployResult0 = await Sub.deploy(signer, { initialFields: { result: 0n } }, exposePrivateFunctions) const subContractId = await getContractIdFromUnsignedTx(nodeProvider, deployResult0.unsignedTx) expect(subContractId).toEqual(deployResult0.contractInstance.contractId) - const deployResult1 = await Add.deploy(signer, { initialFields: { sub: subContractId, result: 0n } }) + const deployResult1 = await Add.deploy( + signer, + { initialFields: { sub: subContractId, result: 0n } }, + exposePrivateFunctions + ) const addContractId = await getContractIdFromUnsignedTx(nodeProvider, deployResult1.unsignedTx) expect(addContractId).toEqual(deployResult1.contractInstance.contractId) }) @@ -154,9 +167,11 @@ describe('contract', function () { }) expect(testResultPrivate.returns).toEqual([3n, 1n]) - const sub = (await Sub.deploy(signer, { initialFields: { result: 0n } })).contractInstance + const sub = (await Sub.deploy(signer, { initialFields: { result: 0n } }, exposePrivateFunctions)).contractInstance expect(sub.groupIndex).toEqual(signerGroup) - const add = (await Add.deploy(signer, { initialFields: { sub: sub.contractId, result: 0n } })).contractInstance + const add = ( + await Add.deploy(signer, { initialFields: { sub: sub.contractId, result: 0n } }, exposePrivateFunctions) + ).contractInstance expect(add.groupIndex).toEqual(signerGroup) // Check state for add/sub before main script is executed @@ -197,8 +212,9 @@ describe('contract', function () { expect(testResult.contracts[0].codeHash).toEqual(Greeter.contract.codeHash) expect(testResult.contracts[0].fields.btcPrice).toEqual(1n) - const greeter = (await Greeter.deploy(signer, { initialFields: { ...initialFields, btcPrice: 1n } })) - .contractInstance + const greeter = ( + await Greeter.deploy(signer, { initialFields: { ...initialFields, btcPrice: 1n } }, exposePrivateFunctions) + ).contractInstance expect(greeter.groupIndex).toEqual(signerGroup) const contractState = await greeter.fetchState() expect(contractState.fields.btcPrice).toEqual(1n) @@ -300,7 +316,7 @@ describe('contract', function () { const contractAddress = randomContractAddress() expectAssertionError(Assert.tests.test({ address: contractAddress }), contractAddress, AssertError) - const assertDeployResult = await Assert.deploy(signer, { initialFields: {} }) + const assertDeployResult = await Assert.deploy(signer, { initialFields: {} }, exposePrivateFunctions) const assertAddress = assertDeployResult.contractInstance.address expectAssertionError( @@ -441,7 +457,7 @@ describe('contract', function () { }, name: '' } - const result = await UserAccount.deploy(signer, { initialFields }) + const result = await UserAccount.deploy(signer, { initialFields }, exposePrivateFunctions) const state = await result.contractInstance.fetchState() expect(state.fields).toEqual(initialFields) @@ -502,9 +518,7 @@ describe('contract', function () { }) it('should test map(integration test)', async () => { - const result = await MapTest.deploy(signer, { - initialFields: {} - }) + const result = await MapTest.deploy(signer, { initialFields: {} }, exposePrivateFunctions) const mapTest = result.contractInstance await InsertIntoMap.execute(signer, { @@ -554,9 +568,14 @@ describe('contract', function () { }) it('should test sign execute method with primitive arguments', async () => { - const sub = await Sub.deploy(signer, { initialFields: { result: 0n } }) - const add = (await Add.deploy(signer, { initialFields: { sub: sub.contractInstance.contractId, result: 0n } })) - .contractInstance + const sub = await Sub.deploy(signer, { initialFields: { result: 0n } }, exposePrivateFunctions) + const add = ( + await Add.deploy( + signer, + { initialFields: { sub: sub.contractInstance.contractId, result: 0n } }, + exposePrivateFunctions + ) + ).contractInstance const caller = (await signer.getSelectedAccount()).address const provider = web3.getCurrentNodeProvider() @@ -568,9 +587,14 @@ describe('contract', function () { }) it('should test sign execute method with array arguments', async () => { - const sub = await Sub.deploy(signer, { initialFields: { result: 0n } }) - const add = (await Add.deploy(signer, { initialFields: { sub: sub.contractInstance.contractId, result: 0n } })) - .contractInstance + const sub = await Sub.deploy(signer, { initialFields: { result: 0n } }, exposePrivateFunctions) + const add = ( + await Add.deploy( + signer, + { initialFields: { sub: sub.contractInstance.contractId, result: 0n } }, + exposePrivateFunctions + ) + ).contractInstance const provider = web3.getCurrentNodeProvider() const stateBefore = await provider.contracts.getContractsAddressState(add.address) @@ -583,9 +607,14 @@ describe('contract', function () { }) it('should test sign execute method with struct arguments', async () => { - const sub = await Sub.deploy(signer, { initialFields: { result: 0n } }) - const add = (await Add.deploy(signer, { initialFields: { sub: sub.contractInstance.contractId, result: 0n } })) - .contractInstance + const sub = await Sub.deploy(signer, { initialFields: { result: 0n } }, exposePrivateFunctions) + const add = ( + await Add.deploy( + signer, + { initialFields: { sub: sub.contractInstance.contractId, result: 0n } }, + exposePrivateFunctions + ) + ).contractInstance const provider = web3.getCurrentNodeProvider() const stateBefore = await provider.contracts.getContractsAddressState(add.address) @@ -613,13 +642,22 @@ describe('contract', function () { it('should test sign execute method with approved assets', async () => { const signerAddress = (await signer.getSelectedAccount()).address - const sub = await Sub.deploy(signer, { - initialFields: { result: 0n }, - issueTokenAmount: 300n, - issueTokenTo: signerAddress - }) - const add = (await Add.deploy(signer, { initialFields: { sub: sub.contractInstance.contractId, result: 0n } })) - .contractInstance + const sub = await Sub.deploy( + signer, + { + initialFields: { result: 0n }, + issueTokenAmount: 300n, + issueTokenTo: signerAddress + }, + exposePrivateFunctions + ) + const add = ( + await Add.deploy( + signer, + { initialFields: { sub: sub.contractInstance.contractId, result: 0n } }, + exposePrivateFunctions + ) + ).contractInstance const provider = web3.getCurrentNodeProvider() const state = await provider.contracts.getContractsAddressState(add.address) @@ -686,9 +724,7 @@ describe('contract', function () { }) it('should call TxScript', async () => { - const result0 = await MapTest.deploy(signer, { - initialFields: {} - }) + const result0 = await MapTest.deploy(signer, { initialFields: {} }, exposePrivateFunctions) const mapTest = result0.contractInstance await InsertIntoMap.execute(signer, { initialFields: { @@ -715,7 +751,7 @@ describe('contract', function () { }, name: '' } - const result1 = await UserAccount.deploy(signer, { initialFields }) + const result1 = await UserAccount.deploy(signer, { initialFields }, exposePrivateFunctions) const userAccount = result1.contractInstance const callResult1 = await CallScript1.call({ @@ -761,4 +797,35 @@ describe('contract', function () { }) expect(BigInt(state2.asset.alphAmount)).toEqual(MINIMAL_CONTRACT_DEPOSIT) }) + + it('should get the contract bytecode for testing', async () => { + expect(Add.contract.publicFunctions().length).not.toEqual(Add.contract.functions.length) + const instance0 = (await Add.deploy(signer, { initialFields: { sub: randomContractId(), result: 0n } }, true)) + .contractInstance + const state0 = await instance0.fetchState() + expect(state0.bytecode).toEqual(Add.contract.getByteCodeForTesting()) + expect(state0.bytecode).not.toEqual(Add.contract.bytecode) + expect(state0.bytecode).not.toEqual(Add.contract.bytecodeDebug) + expect(state0.codeHash).not.toEqual(Add.contract.codeHash) + expect(state0.codeHash).not.toEqual(Add.contract.codeHashDebug) + expect(Add.contract.hasCodeHash(state0.codeHash)).toEqual(true) + + expect(Assert.contract.publicFunctions().length).toEqual(Assert.contract.functions.length) + const instance1 = (await Assert.deploy(signer, { initialFields: {} }, true)).contractInstance + const state1 = await instance1.fetchState() + expect(state1.bytecode).toEqual(Assert.contract.bytecodeDebug) + expect(state1.codeHash).toEqual(Assert.contract.codeHashDebug) + expect(Assert.contract.hasCodeHash(state1.codeHash)).toEqual(true) + }) + + it('should test contract private functions', async () => { + const sub = (await Sub.deploy(signer, { initialFields: { result: 0n } }, true)).contractInstance + const add = (await Add.deploy(signer, { initialFields: { sub: sub.contractId, result: 0n } }, true)) + .contractInstance + await add.transact.addPrivate({ args: { array: [2n, 1n] }, signer }) + const state0 = await add.fetchState() + expect(state0.fields.result).toEqual(3n) + const state1 = await sub.fetchState() + expect(state1.fields.result).toEqual(1n) + }) }) From a018ba6fe363c3657de4368f64a609a6f7f5617d Mon Sep 17 00:00:00 2001 From: lbqds Date: Tue, 10 Sep 2024 16:55:46 +0800 Subject: [PATCH 4/4] Address comments --- packages/web3/src/contract/contract.ts | 41 ++++++------ test/contract.test.ts | 86 ++++++++++++-------------- 2 files changed, 60 insertions(+), 67 deletions(-) diff --git a/packages/web3/src/contract/contract.ts b/packages/web3/src/contract/contract.ts index 5495fa18b..ceca35afc 100644 --- a/packages/web3/src/contract/contract.ts +++ b/packages/web3/src/contract/contract.ts @@ -197,7 +197,7 @@ export class Contract extends Artifact { readonly bytecodeDebug: string readonly codeHashDebug: string - readonly decodedDebugContract: contract.Contract + readonly decodedContract: contract.Contract private bytecodeForTesting: string | undefined private codeHashForTesting: string | undefined @@ -233,7 +233,7 @@ export class Contract extends Artifact { this.bytecodeDebug = ralph.buildDebugBytecode(this.bytecode, this.bytecodeDebugPatch) this.codeHashDebug = codeHashDebug - this.decodedDebugContract = contract.contractCodec.decodeContract(hexToBinUnsafe(this.bytecodeDebug)) + this.decodedContract = contract.contractCodec.decodeContract(hexToBinUnsafe(this.bytecode)) this.bytecodeForTesting = undefined this.codeHashForTesting = undefined } @@ -247,9 +247,10 @@ export class Contract extends Artifact { return this.bytecodeForTesting } - const methods = this.decodedDebugContract.methods.map((method) => ({ ...method, isPublic: true })) + const decodedDebugContract = contract.contractCodec.decodeContract(hexToBinUnsafe(this.bytecodeDebug)) + const methods = decodedDebugContract.methods.map((method) => ({ ...method, isPublic: true })) const bytecodeForTesting = contract.contractCodec.encodeContract({ - fieldLength: this.decodedDebugContract.fieldLength, + fieldLength: decodedDebugContract.fieldLength, methods: methods }) const codeHashForTesting = blake.blake2b(bytecodeForTesting, undefined, 32) @@ -263,7 +264,7 @@ export class Contract extends Artifact { } getDecodedMethod(methodIndex: number): Method { - return this.decodedDebugContract.methods[`${methodIndex}`] + return this.decodedContract.methods[`${methodIndex}`] } publicFunctions(): FunctionSig[] { @@ -563,12 +564,15 @@ export class Contract extends Artifact { async txParamsForDeployment

( signer: SignerProvider, - params: DeployContractParams

, - exposePrivateFunctions = false + params: DeployContractParams

): Promise { const isDevnet = await this.isDevnet(signer) const initialFields: Fields = params.initialFields ?? {} - const bytecode = this.buildByteCodeToDeploy(addStdIdToFields(this, initialFields), isDevnet, exposePrivateFunctions) + const bytecode = this.buildByteCodeToDeploy( + addStdIdToFields(this, initialFields), + isDevnet, + params.exposePrivateFunctions ?? false + ) const selectedAccount = await signer.getSelectedAccount() const signerParams: SignDeployContractTxParams = { signerAddress: selectedAccount.address, @@ -1014,10 +1018,11 @@ export interface DeployContractParams

{ issueTokenTo?: string gasAmount?: number gasPrice?: Number256 + exposePrivateFunctions?: boolean } assertType< Eq< - Omit, 'initialFields'>, + Omit, 'initialFields' | 'exposePrivateFunctions'>, Omit > > @@ -1037,19 +1042,11 @@ export abstract class ContractFactory, - exposePrivateFunctions = false - ): Promise> { - const signerParams = await this.contract.txParamsForDeployment( - signer, - { - ...deployParams, - initialFields: addStdIdToFields(this.contract, deployParams.initialFields) - }, - exposePrivateFunctions - ) + async deploy(signer: SignerProvider, deployParams: DeployContractParams): Promise> { + const signerParams = await this.contract.txParamsForDeployment(signer, { + ...deployParams, + initialFields: addStdIdToFields(this.contract, deployParams.initialFields) + }) const result = await signer.signAndSubmitDeployContractTx(signerParams) return { ...result, diff --git a/test/contract.test.ts b/test/contract.test.ts index e24429f6b..ef524cc5e 100644 --- a/test/contract.test.ts +++ b/test/contract.test.ts @@ -102,15 +102,14 @@ describe('contract', function () { it('should get contract id from tx id', async () => { const nodeProvider = web3.getCurrentNodeProvider() - const deployResult0 = await Sub.deploy(signer, { initialFields: { result: 0n } }, exposePrivateFunctions) + const deployResult0 = await Sub.deploy(signer, { initialFields: { result: 0n }, exposePrivateFunctions }) const subContractId = await getContractIdFromUnsignedTx(nodeProvider, deployResult0.unsignedTx) expect(subContractId).toEqual(deployResult0.contractInstance.contractId) - const deployResult1 = await Add.deploy( - signer, - { initialFields: { sub: subContractId, result: 0n } }, + const deployResult1 = await Add.deploy(signer, { + initialFields: { sub: subContractId, result: 0n }, exposePrivateFunctions - ) + }) const addContractId = await getContractIdFromUnsignedTx(nodeProvider, deployResult1.unsignedTx) expect(addContractId).toEqual(deployResult1.contractInstance.contractId) }) @@ -167,10 +166,10 @@ describe('contract', function () { }) expect(testResultPrivate.returns).toEqual([3n, 1n]) - const sub = (await Sub.deploy(signer, { initialFields: { result: 0n } }, exposePrivateFunctions)).contractInstance + const sub = (await Sub.deploy(signer, { initialFields: { result: 0n }, exposePrivateFunctions })).contractInstance expect(sub.groupIndex).toEqual(signerGroup) const add = ( - await Add.deploy(signer, { initialFields: { sub: sub.contractId, result: 0n } }, exposePrivateFunctions) + await Add.deploy(signer, { initialFields: { sub: sub.contractId, result: 0n }, exposePrivateFunctions }) ).contractInstance expect(add.groupIndex).toEqual(signerGroup) @@ -213,7 +212,7 @@ describe('contract', function () { expect(testResult.contracts[0].fields.btcPrice).toEqual(1n) const greeter = ( - await Greeter.deploy(signer, { initialFields: { ...initialFields, btcPrice: 1n } }, exposePrivateFunctions) + await Greeter.deploy(signer, { initialFields: { ...initialFields, btcPrice: 1n }, exposePrivateFunctions }) ).contractInstance expect(greeter.groupIndex).toEqual(signerGroup) const contractState = await greeter.fetchState() @@ -316,7 +315,7 @@ describe('contract', function () { const contractAddress = randomContractAddress() expectAssertionError(Assert.tests.test({ address: contractAddress }), contractAddress, AssertError) - const assertDeployResult = await Assert.deploy(signer, { initialFields: {} }, exposePrivateFunctions) + const assertDeployResult = await Assert.deploy(signer, { initialFields: {}, exposePrivateFunctions }) const assertAddress = assertDeployResult.contractInstance.address expectAssertionError( @@ -457,7 +456,7 @@ describe('contract', function () { }, name: '' } - const result = await UserAccount.deploy(signer, { initialFields }, exposePrivateFunctions) + const result = await UserAccount.deploy(signer, { initialFields, exposePrivateFunctions }) const state = await result.contractInstance.fetchState() expect(state.fields).toEqual(initialFields) @@ -518,7 +517,7 @@ describe('contract', function () { }) it('should test map(integration test)', async () => { - const result = await MapTest.deploy(signer, { initialFields: {} }, exposePrivateFunctions) + const result = await MapTest.deploy(signer, { initialFields: {}, exposePrivateFunctions }) const mapTest = result.contractInstance await InsertIntoMap.execute(signer, { @@ -568,13 +567,12 @@ describe('contract', function () { }) it('should test sign execute method with primitive arguments', async () => { - const sub = await Sub.deploy(signer, { initialFields: { result: 0n } }, exposePrivateFunctions) + const sub = await Sub.deploy(signer, { initialFields: { result: 0n }, exposePrivateFunctions }) const add = ( - await Add.deploy( - signer, - { initialFields: { sub: sub.contractInstance.contractId, result: 0n } }, + await Add.deploy(signer, { + initialFields: { sub: sub.contractInstance.contractId, result: 0n }, exposePrivateFunctions - ) + }) ).contractInstance const caller = (await signer.getSelectedAccount()).address const provider = web3.getCurrentNodeProvider() @@ -587,13 +585,12 @@ describe('contract', function () { }) it('should test sign execute method with array arguments', async () => { - const sub = await Sub.deploy(signer, { initialFields: { result: 0n } }, exposePrivateFunctions) + const sub = await Sub.deploy(signer, { initialFields: { result: 0n }, exposePrivateFunctions }) const add = ( - await Add.deploy( - signer, - { initialFields: { sub: sub.contractInstance.contractId, result: 0n } }, + await Add.deploy(signer, { + initialFields: { sub: sub.contractInstance.contractId, result: 0n }, exposePrivateFunctions - ) + }) ).contractInstance const provider = web3.getCurrentNodeProvider() @@ -607,13 +604,12 @@ describe('contract', function () { }) it('should test sign execute method with struct arguments', async () => { - const sub = await Sub.deploy(signer, { initialFields: { result: 0n } }, exposePrivateFunctions) + const sub = await Sub.deploy(signer, { initialFields: { result: 0n }, exposePrivateFunctions }) const add = ( - await Add.deploy( - signer, - { initialFields: { sub: sub.contractInstance.contractId, result: 0n } }, + await Add.deploy(signer, { + initialFields: { sub: sub.contractInstance.contractId, result: 0n }, exposePrivateFunctions - ) + }) ).contractInstance const provider = web3.getCurrentNodeProvider() @@ -642,21 +638,17 @@ describe('contract', function () { it('should test sign execute method with approved assets', async () => { const signerAddress = (await signer.getSelectedAccount()).address - const sub = await Sub.deploy( - signer, - { - initialFields: { result: 0n }, - issueTokenAmount: 300n, - issueTokenTo: signerAddress - }, + const sub = await Sub.deploy(signer, { + initialFields: { result: 0n }, + issueTokenAmount: 300n, + issueTokenTo: signerAddress, exposePrivateFunctions - ) + }) const add = ( - await Add.deploy( - signer, - { initialFields: { sub: sub.contractInstance.contractId, result: 0n } }, + await Add.deploy(signer, { + initialFields: { sub: sub.contractInstance.contractId, result: 0n }, exposePrivateFunctions - ) + }) ).contractInstance const provider = web3.getCurrentNodeProvider() @@ -724,7 +716,7 @@ describe('contract', function () { }) it('should call TxScript', async () => { - const result0 = await MapTest.deploy(signer, { initialFields: {} }, exposePrivateFunctions) + const result0 = await MapTest.deploy(signer, { initialFields: {}, exposePrivateFunctions }) const mapTest = result0.contractInstance await InsertIntoMap.execute(signer, { initialFields: { @@ -751,7 +743,7 @@ describe('contract', function () { }, name: '' } - const result1 = await UserAccount.deploy(signer, { initialFields }, exposePrivateFunctions) + const result1 = await UserAccount.deploy(signer, { initialFields, exposePrivateFunctions }) const userAccount = result1.contractInstance const callResult1 = await CallScript1.call({ @@ -800,8 +792,9 @@ describe('contract', function () { it('should get the contract bytecode for testing', async () => { expect(Add.contract.publicFunctions().length).not.toEqual(Add.contract.functions.length) - const instance0 = (await Add.deploy(signer, { initialFields: { sub: randomContractId(), result: 0n } }, true)) - .contractInstance + const instance0 = ( + await Add.deploy(signer, { initialFields: { sub: randomContractId(), result: 0n }, exposePrivateFunctions: true }) + ).contractInstance const state0 = await instance0.fetchState() expect(state0.bytecode).toEqual(Add.contract.getByteCodeForTesting()) expect(state0.bytecode).not.toEqual(Add.contract.bytecode) @@ -811,7 +804,8 @@ describe('contract', function () { expect(Add.contract.hasCodeHash(state0.codeHash)).toEqual(true) expect(Assert.contract.publicFunctions().length).toEqual(Assert.contract.functions.length) - const instance1 = (await Assert.deploy(signer, { initialFields: {} }, true)).contractInstance + const instance1 = (await Assert.deploy(signer, { initialFields: {}, exposePrivateFunctions: true })) + .contractInstance const state1 = await instance1.fetchState() expect(state1.bytecode).toEqual(Assert.contract.bytecodeDebug) expect(state1.codeHash).toEqual(Assert.contract.codeHashDebug) @@ -819,9 +813,11 @@ describe('contract', function () { }) it('should test contract private functions', async () => { - const sub = (await Sub.deploy(signer, { initialFields: { result: 0n } }, true)).contractInstance - const add = (await Add.deploy(signer, { initialFields: { sub: sub.contractId, result: 0n } }, true)) + const sub = (await Sub.deploy(signer, { initialFields: { result: 0n }, exposePrivateFunctions: true })) .contractInstance + const add = ( + await Add.deploy(signer, { initialFields: { sub: sub.contractId, result: 0n }, exposePrivateFunctions: true }) + ).contractInstance await add.transact.addPrivate({ args: { array: [2n, 1n] }, signer }) const state0 = await add.fetchState() expect(state0.fields.result).toEqual(3n)