From ee93120abc7551d22eb94ba7e0521feaffa479bb Mon Sep 17 00:00:00 2001 From: AzaharaC Date: Wed, 17 Mar 2021 14:28:19 +0100 Subject: [PATCH] feat(connector-besu): common interface invoke-contract Signed-off-by: AzaharaC --- .../plugin-ledger-connector-besu.ts | 13 +- .../invoke-contract-endpoint-v2.ts | 6 +- .../invoke-contract-v2.test.ts | 354 ++++++++++++++++++ 3 files changed, 366 insertions(+), 7 deletions(-) create mode 100644 packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/invoke-contract-v2.test.ts diff --git a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts index 6a95cb1ccf..f62dd0f04f 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts +++ b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/plugin-ledger-connector-besu.ts @@ -42,8 +42,8 @@ import { DeployContractSolidityBytecodeV1Response, EthContractInvocationType, InvokeContractV1Request, - InvokeContractV1Response, InvokeContractV2Request, + InvokeContractV1Response, InvokeContractV2Response, RunTransactionRequest, RunTransactionResponse, @@ -298,19 +298,24 @@ export class PluginLedgerConnectorBesu const payload = (method.send as any).request(); const { params } = payload; const [transactionConfig] = params; + if (req.gas == undefined) { + req.gas = await this.web3.eth.estimateGas(transactionConfig); + } transactionConfig.from = web3SigningCredential.ethAccount; transactionConfig.gas = req.gas; transactionConfig.gasPrice = req.gasPrice; transactionConfig.value = req.value; + transactionConfig.nonce = req.nonce; const txReq: RunTransactionRequest = { transactionConfig, web3SigningCredential, timeoutMs: req.timeoutMs || 60000, }; const out = await this.transact(txReq); - const transactionReceipt = out.transactionReceipt; - const success = true; - return { success, transactionReceipt }; + //const transactionReceipt = out.transactionReceipt; + const success = out.transactionReceipt.status; + const data = { success, out }; + return data; } else { throw new Error( `${fnTag} Unsupported invocation type ${req.invocationType}`, diff --git a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/web-services/invoke-contract-endpoint-v2.ts b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/web-services/invoke-contract-endpoint-v2.ts index 5f18ccbadf..45548a0033 100644 --- a/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/web-services/invoke-contract-endpoint-v2.ts +++ b/packages/cactus-plugin-ledger-connector-besu/src/main/typescript/web-services/invoke-contract-endpoint-v2.ts @@ -16,13 +16,13 @@ import { PluginLedgerConnectorBesu } from "../plugin-ledger-connector-besu"; import OAS from "../../json/openapi.json"; -export interface IInvokeContractEndpointOptions { +export interface IInvokeContractEndpointV2Options { logLevel?: LogLevelDesc; connector: PluginLedgerConnectorBesu; } export class InvokeContractEndpointV2 implements IWebServiceEndpoint { - public static readonly CLASS_NAME = "InvokeContractV2Endpoint"; + public static readonly CLASS_NAME = "InvokeContractEndpointV2"; private readonly log: Logger; @@ -30,7 +30,7 @@ export class InvokeContractEndpointV2 implements IWebServiceEndpoint { return InvokeContractEndpointV2.CLASS_NAME; } - constructor(public readonly options: IInvokeContractEndpointOptions) { + constructor(public readonly options: IInvokeContractEndpointV2Options) { const fnTag = `${this.className}#constructor()`; Checks.truthy(options, `${fnTag} arg options`); Checks.truthy(options.connector, `${fnTag} arg options.connector`); diff --git a/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/invoke-contract-v2.test.ts b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/invoke-contract-v2.test.ts new file mode 100644 index 0000000000..a14dafa266 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-besu/src/test/typescript/integration/plugin-ledger-connector-besu/deploy-contract/invoke-contract-v2.test.ts @@ -0,0 +1,354 @@ +import test, { Test } from "tape"; +import { v4 as uuidv4 } from "uuid"; +import path from "path"; +import { PluginRegistry } from "@hyperledger/cactus-core"; +import { + EthContractInvocationType, + Web3SigningCredentialType, + PluginLedgerConnectorBesu, + PluginFactoryLedgerConnector, + Web3SigningCredentialCactusKeychainRef, +} from "../../../../../main/typescript/public-api"; +import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory"; +import { BesuTestLedger } from "@hyperledger/cactus-test-tooling"; +import { LogLevelDesc } from "@hyperledger/cactus-common"; +import HelloWorldContractJson from "../../../../solidity/hello-world-contract/HelloWorld.json"; +import Web3 from "web3"; +import { PluginImportType } from "@hyperledger/cactus-core-api"; +const contractsPath = path.join( + __dirname, + "../../../../solidity/hello-world-contract/HelloWorld.json", +); + +test("deploys contract via .json file", async (t: Test) => { + const logLevel: LogLevelDesc = "TRACE"; + const besuTestLedger = new BesuTestLedger(); + await besuTestLedger.start(); + + test.onFinish(async () => { + await besuTestLedger.stop(); + await besuTestLedger.destroy(); + }); + + const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost(); + + /** + * Constant defining the standard 'dev' Besu genesis.json contents. + * + * @see https://github.com/hyperledger/besu/blob/1.5.1/config/src/main/resources/dev.json + */ + const firstHighNetWorthAccount = "627306090abaB3A6e1400e9345bC60c78a8BEf57"; + const besuKeyPair = { + privateKey: + "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", + }; + const contractName = "HelloWorld"; + + const web3 = new Web3(rpcApiHttpHost); + const testEthAccount = web3.eth.accounts.create(uuidv4()); + + const keychainEntryKey = uuidv4(); + const keychainEntryValue = testEthAccount.privateKey; + const keychainPlugin = new PluginKeychainMemory({ + instanceId: uuidv4(), + keychainId: uuidv4(), + // pre-provision keychain with mock backend holding the private key of the + // test account that we'll reference while sending requests with the + // signing credential pointing to this keychain entry. + backend: new Map([[keychainEntryKey, keychainEntryValue]]), + logLevel, + }); + + const factory = new PluginFactoryLedgerConnector({ + pluginImportType: PluginImportType.LOCAL, + }); + const connector: PluginLedgerConnectorBesu = await factory.create({ + rpcApiHttpHost, + instanceId: uuidv4(), + pluginRegistry: new PluginRegistry({ plugins: [keychainPlugin] }), + contractsPath, + }); + + await connector.transact({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + transactionConfig: { + from: firstHighNetWorthAccount, + to: testEthAccount.address, + value: 10e9, + gas: 1000000, + }, + }); + + const balance = await web3.eth.getBalance(testEthAccount.address); + t.ok(balance, "Retrieved balance of test account OK"); + t.equals(parseInt(balance, 10), 10e9, "Balance of test account is OK"); + + let contractAddress: string; + + test("deploys contract via .json file", async (t2: Test) => { + const deployOut = await connector.deployContract({ + web3SigningCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + bytecode: HelloWorldContractJson.bytecode, + gas: 1000000, + }); + t2.ok(deployOut, "deployContract() output is truthy OK"); + t2.ok( + deployOut.transactionReceipt, + "deployContract() output.transactionReceipt is truthy OK", + ); + t2.ok( + deployOut.transactionReceipt.contractAddress, + "deployContract() output.transactionReceipt.contractAddress is truthy OK", + ); + + contractAddress = deployOut.transactionReceipt.contractAddress as string; + t2.ok( + typeof contractAddress === "string", + "contractAddress typeof string OK", + ); + + const { callOutput: helloMsg } = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.CALL, + methodName: "sayHello", + params: [], + signingCredential: { + ethAccount: firstHighNetWorthAccount, + secret: besuKeyPair.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + }); + t2.ok(helloMsg, "sayHello() output is truthy"); + t2.true( + typeof helloMsg === "string", + "sayHello() output is type of string", + ); + }); + + test("invoke Web3SigningCredentialType.NONE", async (t2: Test) => { + const testEthAccount2 = web3.eth.accounts.create(uuidv4()); + + const { rawTransaction } = await web3.eth.accounts.signTransaction( + { + from: testEthAccount.address, + to: testEthAccount2.address, + value: 10e6, + gas: 1000000, + }, + testEthAccount.privateKey, + ); + + await connector.transact({ + web3SigningCredential: { + type: Web3SigningCredentialType.NONE, + }, + transactionConfig: { + rawTransaction, + }, + }); + + const balance2 = await web3.eth.getBalance(testEthAccount2.address); + t2.ok(balance2, "Retrieved balance of test account 2 OK"); + t2.equals(parseInt(balance2, 10), 10e6, "Balance of test account2 is OK"); + t2.end(); + }); + + test("invoke Web3SigningCredentialType.PRIVATEKEYHEX", async (t2: Test) => { + const newName = `DrCactus${uuidv4()}`; + const setNameOut = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.SEND, + methodName: "setName", + params: [newName], + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + nonce: 1, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.SEND, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + nonce: 1, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + const { callOutput: getNameOut } = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.CALL, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.SEND, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + const response = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.SEND, + methodName: "deposit", + params: [], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + value: 10, + }); + t2.ok(response, "deposit() payable invocation output is truthy OK"); + + const { callOutput } = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.CALL, + methodName: "getNameByIndex", + params: [0], + gas: 1000000, + signingCredential: { + ethAccount: testEthAccount.address, + secret: testEthAccount.privateKey, + type: Web3SigningCredentialType.PRIVATEKEYHEX, + }, + }); + t2.equal( + callOutput, + newName, + `getNameByIndex() output reflects the update OK`, + ); + + t2.end(); + }); + + test("invoke Web3SigningCredentialType.CACTUSKEYCHAINREF", async (t2: Test) => { + const newName = `DrCactus${uuidv4()}`; + const signingCredential: Web3SigningCredentialCactusKeychainRef = { + ethAccount: testEthAccount.address, + keychainEntryKey, + keychainId: keychainPlugin.getKeychainId(), + type: Web3SigningCredentialType.CACTUSKEYCHAINREF, + }; + + const setNameOut = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.SEND, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential, + nonce: 4, + }); + t2.ok(setNameOut, "setName() invocation #1 output is truthy OK"); + + try { + const setNameOutInvalid = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.SEND, + methodName: "setName", + params: [newName], + gas: 1000000, + signingCredential, + nonce: 4, + }); + t2.ifError(setNameOutInvalid.transactionReceipt); + } catch (error) { + t2.notStrictEqual( + error, + "Nonce too low", + "setName() invocation with invalid nonce", + ); + } + + const { callOutput: getNameOut } = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.CALL, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential, + }); + t2.equal(getNameOut, newName, `getName() output reflects the update OK`); + + const getNameOut2 = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.SEND, + methodName: "getName", + params: [], + gas: 1000000, + signingCredential, + }); + t2.ok(getNameOut2, "getName() invocation #2 output is truthy OK"); + + const response = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.SEND, + methodName: "deposit", + params: [], + gas: 1000000, + signingCredential, + value: 10, + }); + t2.ok(response, "deposit() payable invocation output is truthy OK"); + + const { callOutput: callOut } = await connector.invokeContractV2({ + contractName, + invocationType: EthContractInvocationType.CALL, + methodName: "getNameByIndex", + params: [1], + gas: 1000000, + signingCredential, + }); + t2.equal( + callOut, + newName, + `getNameByIndex() output reflects the update OK`, + ); + + t2.end(); + }); + + t.end(); +});