-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1551 from Phala-Network/feat-jssdk-contract-actions
JS-SDK: Contract Actions
- Loading branch information
Showing
16 changed files
with
338 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { convertWeight } from '@polkadot/api-contract/base/util' | ||
import type { ContractCallOutcome } from '@polkadot/api-contract/types' | ||
import type { ContractExecResult } from '@polkadot/types/interfaces' | ||
import { BN_ZERO } from '@polkadot/util' | ||
import type { OnChainRegistry } from '../OnChainRegistry' | ||
import { phalaTypes } from '../options' | ||
import { InkQueryMessage } from '../pruntime/coders' | ||
import { pinkQuery } from '../pruntime/pinkQuery' | ||
import { WorkerAgreementKey } from '../pruntime/WorkerAgreementKey' | ||
import type { FrameSystemAccountInfo, LooseNumber } from '../types' | ||
import { toAbi } from '../utils/abi/toAbi' | ||
import { BN_MAX_SUPPLY } from '../utils/constants' | ||
import { SendPinkCommandParameters } from './sendPinkCommand' | ||
import { SendPinkQueryParameters } from './sendPinkQuery' | ||
|
||
export type EstimateContractParameters<T> = SendPinkQueryParameters<T> & { | ||
contractKey: string | ||
deposit?: LooseNumber | ||
transfer?: LooseNumber | ||
} | ||
|
||
export type EstimateContractResult = Omit<ContractCallOutcome, 'output'> & { | ||
request: SendPinkCommandParameters | ||
} | ||
|
||
export async function estimateContract( | ||
client: OnChainRegistry, | ||
parameters: EstimateContractParameters<any[]> | ||
): Promise<EstimateContractResult> { | ||
const { address, functionName, provider, deposit, transfer } = parameters | ||
if (!client.workerInfo?.pubkey) { | ||
throw new Error('Worker pubkey not found') | ||
} | ||
|
||
const abi = toAbi(parameters.abi) | ||
const args = parameters.args || [] | ||
|
||
const message = abi.findMessage(functionName) | ||
if (!message) { | ||
throw new Error(`Message not found: ${functionName}`) | ||
} | ||
const encodedArgs = message.toU8a(args) | ||
const inkMessage = InkQueryMessage(address, encodedArgs, deposit || BN_MAX_SUPPLY, transfer, true) | ||
const argument = new WorkerAgreementKey(client.workerInfo.pubkey) | ||
|
||
const cert = await provider.signCertificate() | ||
|
||
const [clusterBalance, onchainBalance, inkResponse] = await Promise.all([ | ||
client.getClusterBalance(provider.address), | ||
client.api.query.system.account<FrameSystemAccountInfo>(provider.address), | ||
pinkQuery(client.phactory, argument, inkMessage.toHex(), cert), | ||
]) | ||
|
||
if (inkResponse.result.isErr) { | ||
// @FIXME: not sure this is enough as not yet tested | ||
throw new Error(`InkResponse Error: ${inkResponse.result.asErr.toString()}`) | ||
} | ||
if (!inkResponse.result.asOk.isInkMessageReturn) { | ||
// @FIXME: not sure this is enough as not yet tested | ||
throw new Error(`Unexpected InkMessageReturn: ${inkResponse.result.asOk.toJSON()?.toString()}`) | ||
} | ||
const { debugMessage, gasConsumed, gasRequired, result, storageDeposit } = phalaTypes.createType<ContractExecResult>( | ||
'ContractExecResult', | ||
inkResponse.result.asOk.asInkMessageReturn.toString() | ||
) | ||
|
||
const { gasPrice } = client.clusterInfo ?? {} | ||
if (!gasPrice) { | ||
throw new Error('No Gas Price or deposit Per Byte from cluster info.') | ||
} | ||
|
||
// calculate the total costs | ||
const gasLimit = gasRequired.refTime.toBn() | ||
const storageDepositFee = storageDeposit.isCharge ? storageDeposit.asCharge.toBn() : BN_ZERO | ||
const minRequired = gasLimit.mul(gasPrice).add(storageDepositFee) | ||
|
||
// Auto deposit. | ||
let autoDeposit = undefined | ||
if (clusterBalance.free.lt(minRequired)) { | ||
const deposit = minRequired.sub(clusterBalance.free) | ||
if (onchainBalance.data.free.lt(deposit)) { | ||
throw new Error(`Not enough balance to pay for gas and storage deposit: ${minRequired.toNumber()}`) | ||
} | ||
autoDeposit = deposit | ||
} | ||
|
||
return { | ||
debugMessage: debugMessage, | ||
gasConsumed: gasConsumed, | ||
gasRequired: gasRequired && !convertWeight(gasRequired).v1Weight.isZero() ? gasRequired : gasConsumed, | ||
result, | ||
storageDeposit, | ||
request: { | ||
...parameters, | ||
gasLimit, | ||
deposit: autoDeposit, | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { convertWeight } from '@polkadot/api-contract/base/util' | ||
import { BN_ZERO, hexAddPrefix, isHex } from '@polkadot/util' | ||
import type { OnChainRegistry } from '../OnChainRegistry' | ||
import { EncryptedInkCommand, PlainInkCommand } from '../pruntime/coders' | ||
import type { AbiLike, AnyProvider, HexString, LooseNumber } from '../types' | ||
import { toAbi } from '../utils/abi/toAbi' | ||
import assert from '../utils/assert' | ||
import { randomHex } from '../utils/hex' | ||
|
||
export type SendPinkCommandParameters<TArgs = any[]> = { | ||
address: string | ||
contractKey: string | ||
provider: AnyProvider | ||
abi: AbiLike | ||
functionName: string | ||
args?: TArgs | ||
// | ||
gasLimit: LooseNumber | ||
nonce?: HexString | ||
plain?: boolean | ||
value?: LooseNumber | ||
storageDepositLimit?: LooseNumber | ||
deposit?: LooseNumber | ||
} | ||
|
||
export async function sendPinkCommand(client: OnChainRegistry, parameters: SendPinkCommandParameters) { | ||
const { address, contractKey, functionName, provider, plain, value, gasLimit, storageDepositLimit, deposit } = | ||
parameters | ||
if (!client.workerInfo?.pubkey) { | ||
throw new Error('Worker pubkey not found') | ||
} | ||
|
||
const abi = toAbi(parameters.abi) | ||
const args = parameters.args || [] | ||
|
||
parameters.nonce && assert(isHex(parameters.nonce) && parameters.nonce.length === 66, 'Invalid nonce provided') | ||
const nonce = parameters.nonce || hexAddPrefix(randomHex(32)) | ||
|
||
const message = abi.findMessage(functionName) | ||
if (!message) { | ||
throw new Error(`Message not found: ${functionName}`) | ||
} | ||
const encodedArgs = message.toU8a(args) | ||
|
||
const createCommand = plain ? PlainInkCommand : EncryptedInkCommand | ||
const payload = createCommand( | ||
contractKey, | ||
encodedArgs, | ||
nonce, | ||
value, | ||
convertWeight(gasLimit || BN_ZERO).v2Weight, | ||
storageDepositLimit | ||
) | ||
const extrinsic = client.api.tx.phalaPhatContracts.pushContractMessage(address, payload.toHex(), deposit || BN_ZERO) | ||
const result = await provider.send(extrinsic) | ||
return result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import type { ContractExecResult } from '@polkadot/types/interfaces' | ||
import type { Codec } from '@polkadot/types-codec/types' | ||
import type { OnChainRegistry } from '../OnChainRegistry' | ||
import { phalaTypes } from '../options' | ||
import { InkQueryMessage } from '../pruntime/coders' | ||
import { pinkQuery } from '../pruntime/pinkQuery' | ||
import { WorkerAgreementKey } from '../pruntime/WorkerAgreementKey' | ||
import type { AbiLike, AnyProvider } from '../types' | ||
import { toAbi } from '../utils/abi/toAbi' | ||
|
||
export type SendPinkQueryParameters<TArgs = any[]> = { | ||
address: string | ||
provider: AnyProvider | ||
abi: AbiLike | ||
functionName: string | ||
args?: TArgs | ||
} | ||
|
||
export async function sendPinkQuery<TResult extends Codec = Codec>( | ||
client: OnChainRegistry, | ||
parameters: SendPinkQueryParameters | ||
): Promise<TResult | null> { | ||
const { address, functionName, provider } = parameters | ||
if (!client.workerInfo?.pubkey) { | ||
throw new Error('Worker pubkey not found') | ||
} | ||
|
||
const abi = toAbi(parameters.abi) | ||
const args = parameters.args || [] | ||
|
||
const message = abi.findMessage(functionName) | ||
if (!message) { | ||
throw new Error(`Message not found: ${functionName}`) | ||
} | ||
const encodedArgs = message.toU8a(args) | ||
const inkMessage = InkQueryMessage(address, encodedArgs) | ||
const argument = new WorkerAgreementKey(client.workerInfo.pubkey) | ||
|
||
const cert = await provider.signCertificate() | ||
const inkResponse = await pinkQuery(client.phactory, argument, inkMessage.toHex(), cert) | ||
|
||
if (inkResponse.result.isErr) { | ||
// @FIXME: not sure this is enough as not yet tested | ||
throw new Error(`InkResponse Error: ${inkResponse.result.asErr.toString()}`) | ||
} | ||
if (!inkResponse.result.asOk.isInkMessageReturn) { | ||
// @FIXME: not sure this is enough as not yet tested | ||
throw new Error(`Unexpected InkMessageReturn: ${inkResponse.result.asOk.toJSON()?.toString()}`) | ||
} | ||
const { result } = phalaTypes.createType<ContractExecResult>( | ||
'ContractExecResult', | ||
inkResponse.result.asOk.asInkMessageReturn.toString() | ||
) | ||
if (result.isErr) { | ||
throw new Error(`ContractExecResult Error: ${result.asErr.toString()}`) | ||
} | ||
if (message.returnType) { | ||
return abi.registry.createTypeUnsafe<TResult>( | ||
message.returnType.lookupName || message.returnType.type, | ||
[result.asOk.data.toU8a(true)], | ||
{ isPedantic: true } | ||
) | ||
} | ||
return null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.