@alephium/web3 0.4.0 → 0.5.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,26 +33,39 @@ import {
33
33
  Token,
34
34
  Val,
35
35
  fromApiTokens,
36
- fromApiVals
36
+ fromApiVals,
37
+ typeLength
37
38
  } from '../api'
38
39
  import {
39
40
  SignDeployContractTxParams,
41
+ SignDeployContractTxResult,
40
42
  SignExecuteScriptTxParams,
41
43
  SignerProvider,
42
- SignExecuteScriptTxResult,
43
- SignDeployContractTxResult
44
+ Address
44
45
  } from '../signer'
45
46
  import * as ralph from './ralph'
46
- import { bs58, binToHex, contractIdFromAddress, assertType, Eq } from '../utils'
47
+ import {
48
+ bs58,
49
+ binToHex,
50
+ contractIdFromAddress,
51
+ SubscribeOptions,
52
+ Subscription,
53
+ assertType,
54
+ Eq,
55
+ Optional,
56
+ groupOfAddress
57
+ } from '../utils'
47
58
  import { getCurrentNodeProvider } from '../global'
48
- import { web3 } from '..'
49
59
  import * as path from 'path'
60
+ import { EventSubscription, subscribeToEvents } from './events'
61
+ import { ONE_ALPH } from '../constants'
50
62
 
51
63
  export type FieldsSig = node.FieldsSig
52
64
  export type EventSig = node.EventSig
53
65
  export type FunctionSig = node.FunctionSig
54
66
  export type Fields = NamedVals
55
67
  export type Arguments = NamedVals
68
+ export type HexString = string
56
69
 
57
70
  enum SourceKind {
58
71
  Contract = 0,
@@ -354,7 +367,7 @@ export class Project {
354
367
  return script.artifact
355
368
  }
356
369
 
357
- private async saveArtifactsToFile(): Promise<void> {
370
+ private async saveArtifactsToFile(projectRootDir: string): Promise<void> {
358
371
  const artifactsRootDir = this.artifactsRootDir
359
372
  const saveToFile = async function (compiled: Compiled<Artifact>): Promise<void> {
360
373
  const artifactPath = compiled.sourceInfo.getArtifactPath(artifactsRootDir)
@@ -366,7 +379,7 @@ export class Project {
366
379
  }
367
380
  this.contracts.forEach((contract) => saveToFile(contract))
368
381
  this.scripts.forEach((script) => saveToFile(script))
369
- await this.projectArtifact.saveToFile(this.artifactsRootDir)
382
+ await this.projectArtifact.saveToFile(projectRootDir)
370
383
  }
371
384
 
372
385
  contractByCodeHash(codeHash: string): Contract {
@@ -382,6 +395,7 @@ export class Project {
382
395
  private static async compile(
383
396
  provider: NodeProvider,
384
397
  sourceInfos: SourceInfo[],
398
+ projectRootDir: string,
385
399
  contractsRootDir: string,
386
400
  artifactsRootDir: string,
387
401
  errorOnWarnings: boolean,
@@ -414,7 +428,7 @@ export class Project {
414
428
  errorOnWarnings,
415
429
  projectArtifact
416
430
  )
417
- await project.saveArtifactsToFile()
431
+ await project.saveArtifactsToFile(projectRootDir)
418
432
  return project
419
433
  }
420
434
 
@@ -422,6 +436,7 @@ export class Project {
422
436
  provider: NodeProvider,
423
437
  sourceInfos: SourceInfo[],
424
438
  projectArtifact: ProjectArtifact,
439
+ projectRootDir: string,
425
440
  contractsRootDir: string,
426
441
  artifactsRootDir: string,
427
442
  errorOnWarnings: boolean,
@@ -460,6 +475,7 @@ export class Project {
460
475
  return Project.compile(
461
476
  provider,
462
477
  sourceInfos,
478
+ projectRootDir,
463
479
  contractsRootDir,
464
480
  artifactsRootDir,
465
481
  errorOnWarnings,
@@ -590,12 +606,13 @@ export class Project {
590
606
  const provider = getCurrentNodeProvider()
591
607
  const sourceFiles = await Project.loadSourceFiles(projectRootDir, contractsRootDir)
592
608
  const { errorOnWarnings, ...nodeCompilerOptions } = { ...DEFAULT_COMPILER_OPTIONS, ...compilerOptionsPartial }
593
- const projectArtifact = await ProjectArtifact.from(artifactsRootDir)
609
+ const projectArtifact = await ProjectArtifact.from(projectRootDir)
594
610
  if (typeof projectArtifact === 'undefined' || projectArtifact.needToReCompile(nodeCompilerOptions, sourceFiles)) {
595
611
  console.log(`Compiling contracts in folder "${contractsRootDir}"`)
596
612
  Project.currentProject = await Project.compile(
597
613
  provider,
598
614
  sourceFiles,
615
+ projectRootDir,
599
616
  contractsRootDir,
600
617
  artifactsRootDir,
601
618
  errorOnWarnings,
@@ -607,6 +624,7 @@ export class Project {
607
624
  provider,
608
625
  sourceFiles,
609
626
  projectArtifact,
627
+ projectRootDir,
610
628
  contractsRootDir,
611
629
  artifactsRootDir,
612
630
  errorOnWarnings,
@@ -722,13 +740,6 @@ export class Contract extends Artifact {
722
740
  return Contract.fromJson(artifact, bytecodeDebugPatch, codeHashDebug)
723
741
  }
724
742
 
725
- async fetchState(address: string, group: number): Promise<ContractState> {
726
- const state = await web3.getCurrentNodeProvider().contracts.getContractsAddressState(address, {
727
- group: group
728
- })
729
- return this.fromApiContractState(state)
730
- }
731
-
732
743
  override toString(): string {
733
744
  const object = {
734
745
  version: this.version,
@@ -742,7 +753,7 @@ export class Contract extends Artifact {
742
753
  return JSON.stringify(object, null, 2)
743
754
  }
744
755
 
745
- toState(fields: Fields, asset: Asset, address?: string): ContractState {
756
+ toState<T extends Fields>(fields: T, asset: Asset, address?: string): ContractState<T> {
746
757
  const addressDef = typeof address !== 'undefined' ? address : Contract.randomAddress()
747
758
  return {
748
759
  address: addressDef,
@@ -763,53 +774,13 @@ export class Contract extends Artifact {
763
774
  return bs58.encode(bytes)
764
775
  }
765
776
 
766
- private _printDebugMessages(funcName: string, messages: DebugMessage[]) {
777
+ printDebugMessages(funcName: string, messages: DebugMessage[]) {
767
778
  if (messages.length != 0) {
768
779
  console.log(`Testing ${this.name}.${funcName}:`)
769
780
  messages.forEach((m) => console.log(`Debug - ${m.contractAddress} - ${m.message}`))
770
781
  }
771
782
  }
772
783
 
773
- private async _test(
774
- funcName: string,
775
- params: TestContractParams,
776
- expectPublic: boolean,
777
- accessType: string,
778
- printDebugMessages: boolean
779
- ): Promise<TestContractResult> {
780
- const apiParams: node.TestContract = this.toTestContract(funcName, params)
781
- const apiResult = await web3.getCurrentNodeProvider().contracts.postContractsTestContract(apiParams)
782
-
783
- const methodIndex =
784
- typeof params.testMethodIndex !== 'undefined' ? params.testMethodIndex : this.getMethodIndex(funcName)
785
- const isPublic = this.functions[`${methodIndex}`].isPublic
786
- if (printDebugMessages) {
787
- this._printDebugMessages(funcName, apiResult.debugMessages)
788
- }
789
- if (isPublic === expectPublic) {
790
- const result = await this.fromTestContractResult(methodIndex, apiResult)
791
- return result
792
- } else {
793
- throw new Error(`The test method ${funcName} is not ${accessType}`)
794
- }
795
- }
796
-
797
- async testPublicMethod(
798
- funcName: string,
799
- params: TestContractParams,
800
- printDebugMessages = true
801
- ): Promise<TestContractResult> {
802
- return this._test(funcName, params, true, 'public', printDebugMessages)
803
- }
804
-
805
- async testPrivateMethod(
806
- funcName: string,
807
- params: TestContractParams,
808
- printDebugMessages = true
809
- ): Promise<TestContractResult> {
810
- return this._test(funcName, params, false, 'private', printDebugMessages)
811
- }
812
-
813
784
  toApiFields(fields?: Fields): node.Val[] {
814
785
  if (typeof fields === 'undefined') {
815
786
  return []
@@ -839,12 +810,17 @@ export class Contract extends Artifact {
839
810
  return typeof states != 'undefined' ? states.map((state) => toApiContractState(state)) : undefined
840
811
  }
841
812
 
842
- toTestContract(funcName: string, params: TestContractParams): node.TestContract {
813
+ toApiTestContractParams(funcName: string, params: TestContractParams): node.TestContract {
814
+ const immFields =
815
+ params.initialFields === undefined ? [] : extractFields(params.initialFields, this.fieldsSig, false)
816
+ const mutFields =
817
+ params.initialFields === undefined ? [] : extractFields(params.initialFields, this.fieldsSig, true)
843
818
  return {
844
819
  group: params.group,
845
820
  address: params.address,
846
821
  bytecode: this.bytecodeDebug,
847
- initialFields: this.toApiFields(params.initialFields),
822
+ initialImmFields: immFields,
823
+ initialMutFields: mutFields,
848
824
  initialAsset: typeof params.initialAsset !== 'undefined' ? toApiAsset(params.initialAsset) : undefined,
849
825
  methodIndex: this.getMethodIndex(funcName),
850
826
  args: this.toApiArgs(funcName, params.testArgs),
@@ -853,14 +829,14 @@ export class Contract extends Artifact {
853
829
  }
854
830
  }
855
831
 
856
- fromApiContractState(state: node.ContractState): ContractState {
832
+ fromApiContractState(state: node.ContractState): ContractState<Fields> {
857
833
  return {
858
834
  address: state.address,
859
835
  contractId: binToHex(contractIdFromAddress(state.address)),
860
836
  bytecode: state.bytecode,
861
837
  initialStateHash: state.initialStateHash,
862
838
  codeHash: state.codeHash,
863
- fields: fromApiFields(state.fields, this.fieldsSig),
839
+ fields: fromApiFields(state.immFields, state.mutFields, this.fieldsSig),
864
840
  fieldsSig: this.fieldsSig,
865
841
  asset: fromApiAsset(state.asset)
866
842
  }
@@ -871,24 +847,26 @@ export class Contract extends Artifact {
871
847
  return contract.fromApiContractState(state)
872
848
  }
873
849
 
850
+ static ContractCreatedEventIndex = -1
874
851
  static ContractCreatedEvent: EventSig = {
875
852
  name: 'ContractCreated',
876
853
  fieldNames: ['address'],
877
854
  fieldTypes: ['Address']
878
855
  }
879
856
 
857
+ static ContractDestroyedEventIndex = -2
880
858
  static ContractDestroyedEvent: EventSig = {
881
859
  name: 'ContractDestroyed',
882
860
  fieldNames: ['address'],
883
861
  fieldTypes: ['Address']
884
862
  }
885
863
 
886
- static fromApiEvent(event: node.ContractEventByTxId, codeHash: string | undefined): ContractEventByTxId {
864
+ static fromApiEvent(event: node.ContractEventByTxId, codeHash: string | undefined, txId: string): ContractEvent {
887
865
  let eventSig: EventSig
888
866
 
889
- if (event.eventIndex == -1) {
867
+ if (event.eventIndex == Contract.ContractCreatedEventIndex) {
890
868
  eventSig = this.ContractCreatedEvent
891
- } else if (event.eventIndex == -2) {
869
+ } else if (event.eventIndex == Contract.ContractDestroyedEventIndex) {
892
870
  eventSig = this.ContractDestroyedEvent
893
871
  } else {
894
872
  const contract = Project.currentProject.contractByCodeHash(codeHash!)
@@ -896,65 +874,114 @@ export class Contract extends Artifact {
896
874
  }
897
875
 
898
876
  return {
877
+ txId: txId,
899
878
  blockHash: event.blockHash,
900
879
  contractAddress: event.contractAddress,
901
880
  name: eventSig.name,
881
+ eventIndex: event.eventIndex,
902
882
  fields: fromApiEventFields(event.fields, eventSig)
903
883
  }
904
884
  }
905
885
 
906
- fromTestContractResult(methodIndex: number, result: node.TestContractResult): TestContractResult {
886
+ fromApiTestContractResult(
887
+ methodName: string,
888
+ result: node.TestContractResult,
889
+ txId: string
890
+ ): TestContractResult<unknown> {
891
+ const methodIndex = this.functions.findIndex((sig) => sig.name === methodName)
892
+ const returnTypes = this.functions[`${methodIndex}`].returnTypes
893
+ const rawReturn = fromApiArray(result.returns, returnTypes)
894
+ const returns = rawReturn.length === 0 ? null : rawReturn.length === 1 ? rawReturn[0] : rawReturn
895
+
907
896
  const addressToCodeHash = new Map<string, string>()
908
897
  addressToCodeHash.set(result.address, result.codeHash)
909
898
  result.contracts.forEach((contract) => addressToCodeHash.set(contract.address, contract.codeHash))
910
899
  return {
911
900
  contractId: binToHex(contractIdFromAddress(result.address)),
912
901
  contractAddress: result.address,
913
- returns: fromApiArray(result.returns, this.functions[`${methodIndex}`].returnTypes),
902
+ returns: returns,
914
903
  gasUsed: result.gasUsed,
915
904
  contracts: result.contracts.map((contract) => Contract.fromApiContractState(contract)),
916
905
  txOutputs: result.txOutputs.map(fromApiOutput),
917
- events: result.events.map((event) => {
918
- const contractAddress = event.contractAddress
919
- const codeHash = addressToCodeHash.get(contractAddress)
920
- if (typeof codeHash !== 'undefined' || event.eventIndex < 0) {
921
- return Contract.fromApiEvent(event, codeHash)
922
- } else {
923
- throw Error(`Cannot find codeHash for the contract address: ${contractAddress}`)
924
- }
925
- }),
906
+ events: Contract.fromApiEvents(result.events, addressToCodeHash, txId),
926
907
  debugMessages: result.debugMessages
927
908
  }
928
909
  }
929
910
 
930
- async txParamsForDeployment(
911
+ async txParamsForDeployment<P extends Fields>(
931
912
  signer: SignerProvider,
932
- params: Omit<BuildDeployContractTx, 'signerAddress'>
913
+ params: DeployContractParams<P>
933
914
  ): Promise<SignDeployContractTxParams> {
934
- const bytecode = this.buildByteCodeToDeploy(params.initialFields ? params.initialFields : {})
915
+ const bytecode = this.buildByteCodeToDeploy(params.initialFields ?? {})
935
916
  const signerParams: SignDeployContractTxParams = {
936
917
  signerAddress: await signer.getSelectedAddress(),
937
918
  bytecode: bytecode,
938
- initialAttoAlphAmount: params.initialAttoAlphAmount,
939
- issueTokenAmount: params.issueTokenAmount,
940
- initialTokenAmounts: params.initialTokenAmounts,
941
- gasAmount: params.gasAmount,
942
- gasPrice: params.gasPrice
919
+ initialAttoAlphAmount: params?.initialAttoAlphAmount,
920
+ issueTokenAmount: params?.issueTokenAmount,
921
+ initialTokenAmounts: params?.initialTokenAmounts,
922
+ gasAmount: params?.gasAmount,
923
+ gasPrice: params?.gasPrice
943
924
  }
944
925
  return signerParams
945
926
  }
946
927
 
947
- async deploy(
948
- signer: SignerProvider,
949
- params: Omit<BuildDeployContractTx, 'signerAddress'>
950
- ): Promise<SignDeployContractTxResult> {
951
- const signerParams = await this.txParamsForDeployment(signer, params)
952
- return signer.signAndSubmitDeployContractTx(signerParams)
953
- }
954
-
955
928
  buildByteCodeToDeploy(initialFields: Fields): string {
956
929
  return ralph.buildContractByteCode(this.bytecode, initialFields, this.fieldsSig)
957
930
  }
931
+
932
+ static fromApiEvents(
933
+ events: node.ContractEventByTxId[],
934
+ addressToCodeHash: Map<string, string>,
935
+ txId: string
936
+ ): ContractEvent[] {
937
+ return events.map((event) => {
938
+ const contractAddress = event.contractAddress
939
+ const codeHash = addressToCodeHash.get(contractAddress)
940
+ if (typeof codeHash !== 'undefined' || event.eventIndex < 0) {
941
+ return Contract.fromApiEvent(event, codeHash, txId)
942
+ } else {
943
+ throw Error(`Cannot find codeHash for the contract address: ${contractAddress}`)
944
+ }
945
+ })
946
+ }
947
+
948
+ toApiCallContract<T extends Arguments>(
949
+ params: CallContractParams<T>,
950
+ groupIndex: number,
951
+ contractAddress: string,
952
+ methodIndex: number
953
+ ): node.CallContract {
954
+ const functionSig = this.functions[`${methodIndex}`]
955
+ const args = toApiVals(params.args ?? {}, functionSig.paramNames, functionSig.paramTypes)
956
+ return {
957
+ ...params,
958
+ group: groupIndex,
959
+ address: contractAddress,
960
+ methodIndex: methodIndex,
961
+ args: args
962
+ }
963
+ }
964
+
965
+ fromApiCallContractResult(
966
+ result: node.CallContractResult,
967
+ txId: string,
968
+ methodIndex: number
969
+ ): CallContractResult<unknown> {
970
+ const returnTypes = this.functions[`${methodIndex}`].returnTypes
971
+ const rawReturn = fromApiArray(result.returns, returnTypes)
972
+ const returns = rawReturn.length === 0 ? null : rawReturn.length === 1 ? rawReturn[0] : rawReturn
973
+
974
+ const addressToCodeHash = new Map<string, string>()
975
+ result.contracts.forEach((contract) => addressToCodeHash.set(contract.address, contract.codeHash))
976
+ return {
977
+ returns: returns,
978
+ gasUsed: result.gasUsed,
979
+ contracts: result.contracts.map((state) => Contract.fromApiContractState(state)),
980
+ txInputs: result.txInputs,
981
+ txOutputs: result.txOutputs.map((output) => fromApiOutput(output)),
982
+ events: Contract.fromApiEvents(result.events, addressToCodeHash, txId)
983
+ }
984
+ }
958
985
  }
959
986
 
960
987
  export class Script extends Artifact {
@@ -1025,13 +1052,13 @@ export class Script extends Artifact {
1025
1052
  return JSON.stringify(object, null, 2)
1026
1053
  }
1027
1054
 
1028
- async txParamsForExecution(
1055
+ async txParamsForExecution<P extends Fields>(
1029
1056
  signer: SignerProvider,
1030
- params: Omit<BuildExecuteScriptTx, 'signerAddress'>
1057
+ params: ExecuteScriptParams<P>
1031
1058
  ): Promise<SignExecuteScriptTxParams> {
1032
1059
  const signerParams: SignExecuteScriptTxParams = {
1033
1060
  signerAddress: await signer.getSelectedAddress(),
1034
- bytecode: this.buildByteCodeToDeploy(params.initialFields ? params.initialFields : {}),
1061
+ bytecode: this.buildByteCodeToDeploy(params.initialFields ?? {}),
1035
1062
  attoAlphAmount: params.attoAlphAmount,
1036
1063
  tokens: params.tokens,
1037
1064
  gasAmount: params.gasAmount,
@@ -1040,20 +1067,27 @@ export class Script extends Artifact {
1040
1067
  return signerParams
1041
1068
  }
1042
1069
 
1043
- async execute(
1044
- signer: SignerProvider,
1045
- params: Omit<BuildExecuteScriptTx, 'signerAddress'>
1046
- ): Promise<SignExecuteScriptTxResult> {
1047
- const signerParams = await this.txParamsForExecution(signer, params)
1048
- return await signer.signAndSubmitExecuteScriptTx(signerParams)
1049
- }
1050
-
1051
1070
  buildByteCodeToDeploy(initialFields: Fields): string {
1052
1071
  return ralph.buildScriptByteCode(this.bytecodeTemplate, initialFields, this.fieldsSig)
1053
1072
  }
1054
1073
  }
1055
1074
 
1056
- function fromApiFields(vals: node.Val[], fieldsSig: node.FieldsSig): Fields {
1075
+ function fromApiFields(immFields: node.Val[], mutFields: node.Val[], fieldsSig: node.FieldsSig): Fields {
1076
+ const vals: node.Val[] = []
1077
+ let immIndex = 0
1078
+ let mutIndex = 0
1079
+ const isMutable = fieldsSig.types.flatMap((tpe, index) =>
1080
+ Array(typeLength(tpe)).fill(fieldsSig.isMutable[`${index}`])
1081
+ )
1082
+ isMutable.forEach((mutable) => {
1083
+ if (mutable) {
1084
+ vals.push(mutFields[`${mutIndex}`])
1085
+ mutIndex += 1
1086
+ } else {
1087
+ vals.push(immFields[`${immIndex}`])
1088
+ immIndex += 1
1089
+ }
1090
+ })
1057
1091
  return fromApiVals(vals, fieldsSig.names, fieldsSig.types)
1058
1092
  }
1059
1093
 
@@ -1085,13 +1119,13 @@ export interface InputAsset {
1085
1119
  asset: Asset
1086
1120
  }
1087
1121
 
1088
- export interface ContractState {
1122
+ export interface ContractState<T extends Fields = Fields> {
1089
1123
  address: string
1090
1124
  contractId: string
1091
1125
  bytecode: string
1092
1126
  initialStateHash?: string
1093
1127
  codeHash: string
1094
- fields: Fields
1128
+ fields: T
1095
1129
  fieldsSig: FieldsSig
1096
1130
  asset: Asset
1097
1131
  }
@@ -1104,13 +1138,24 @@ function getVal(vals: NamedVals, name: string): Val {
1104
1138
  }
1105
1139
  }
1106
1140
 
1141
+ function extractFields(fields: NamedVals, fieldsSig: FieldsSig, mutable: boolean) {
1142
+ const fieldIndexes = fieldsSig.names
1143
+ .map((_, index) => index)
1144
+ .filter((index) => fieldsSig.isMutable[`${index}`] === mutable)
1145
+ const fieldNames = fieldIndexes.map((index) => fieldsSig.names[`${index}`])
1146
+ const fieldTypes = fieldIndexes.map((index) => fieldsSig.types[`${index}`])
1147
+ return toApiVals(fields, fieldNames, fieldTypes)
1148
+ }
1149
+
1107
1150
  function toApiContractState(state: ContractState): node.ContractState {
1151
+ const stateFields = state.fields ?? {}
1108
1152
  return {
1109
1153
  address: state.address,
1110
1154
  bytecode: state.bytecode,
1111
1155
  codeHash: state.codeHash,
1112
1156
  initialStateHash: state.initialStateHash,
1113
- fields: toApiFields(state.fields, state.fieldsSig),
1157
+ immFields: extractFields(stateFields, state.fieldsSig, false),
1158
+ mutFields: extractFields(stateFields, state.fieldsSig, true),
1114
1159
  asset: toApiAsset(state.asset)
1115
1160
  }
1116
1161
  }
@@ -1123,7 +1168,7 @@ function toApiArgs(args: Arguments, funcSig: FunctionSig): node.Val[] {
1123
1168
  return toApiVals(args, funcSig.paramNames, funcSig.paramTypes)
1124
1169
  }
1125
1170
 
1126
- function toApiVals(fields: Fields, names: string[], types: string[]): node.Val[] {
1171
+ export function toApiVals(fields: Fields, names: string[], types: string[]): node.Val[] {
1127
1172
  return names.map((name, index) => {
1128
1173
  const val = getVal(fields, name)
1129
1174
  const tpe = types[`${index}`]
@@ -1139,41 +1184,37 @@ function toApiInputAssets(inputAssets?: InputAsset[]): node.TestInputAsset[] | u
1139
1184
  return typeof inputAssets !== 'undefined' ? inputAssets.map(toApiInputAsset) : undefined
1140
1185
  }
1141
1186
 
1142
- export interface TestContractParams {
1187
+ export interface TestContractParams<F extends Fields = Fields, A extends Arguments = Arguments> {
1143
1188
  group?: number // default 0
1144
1189
  address?: string
1145
- initialFields?: Fields // default no fields
1190
+ blockHash?: string
1191
+ txId?: string
1192
+ initialFields: F
1146
1193
  initialAsset?: Asset // default 1 ALPH
1147
- testMethodIndex?: number // default 0
1148
- testArgs?: Arguments // default no arguments
1194
+ testArgs: A
1149
1195
  existingContracts?: ContractState[] // default no existing contracts
1150
1196
  inputAssets?: InputAsset[] // default no input asserts
1151
1197
  }
1152
1198
 
1153
- export interface ContractEvent {
1154
- blockHash: string
1199
+ export interface ContractEvent<T extends Fields = Fields> {
1155
1200
  txId: string
1156
- name: string
1157
- fields: Fields
1158
- }
1159
-
1160
- export interface ContractEventByTxId {
1161
1201
  blockHash: string
1162
1202
  contractAddress: string
1203
+ eventIndex: number
1163
1204
  name: string
1164
- fields: Fields
1205
+ fields: T
1165
1206
  }
1166
1207
 
1167
1208
  export type DebugMessage = node.DebugMessage
1168
1209
 
1169
- export interface TestContractResult {
1210
+ export interface TestContractResult<R> {
1170
1211
  contractId: string
1171
1212
  contractAddress: string
1172
- returns: Val[]
1213
+ returns: R
1173
1214
  gasUsed: number
1174
1215
  contracts: ContractState[]
1175
1216
  txOutputs: Output[]
1176
- events: ContractEventByTxId[]
1217
+ events: ContractEvent[]
1177
1218
  debugMessages: DebugMessage[]
1178
1219
  }
1179
1220
  export declare type Output = AssetOutput | ContractOutput
@@ -1214,41 +1255,329 @@ function fromApiOutput(output: node.Output): Output {
1214
1255
  }
1215
1256
  }
1216
1257
 
1217
- export interface DeployContractTransaction {
1218
- fromGroup: number
1219
- toGroup: number
1220
- unsignedTx: string
1221
- txId: string
1222
- contractAddress: string
1223
- contractId: string
1258
+ export function randomTxId(): string {
1259
+ const bytes = new Uint8Array(32)
1260
+ crypto.getRandomValues(bytes)
1261
+ return binToHex(bytes)
1224
1262
  }
1225
1263
 
1226
- type BuildTxParams<T> = Omit<T, 'bytecode'> & { initialFields?: Val[] }
1227
-
1228
- export interface BuildDeployContractTx {
1229
- signerAddress: string
1230
- initialFields?: Fields
1264
+ export interface DeployContractParams<P extends Fields = Fields> {
1265
+ initialFields: P
1231
1266
  initialAttoAlphAmount?: Number256
1232
1267
  initialTokenAmounts?: Token[]
1233
1268
  issueTokenAmount?: Number256
1234
1269
  gasAmount?: number
1235
1270
  gasPrice?: Number256
1236
1271
  }
1237
- assertType<Eq<keyof BuildDeployContractTx, keyof BuildTxParams<SignDeployContractTxParams>>>()
1272
+ assertType<
1273
+ Eq<
1274
+ Omit<DeployContractParams<undefined>, 'initialFields'>,
1275
+ Omit<SignDeployContractTxParams, 'signerAddress' | 'bytecode'>
1276
+ >
1277
+ >
1278
+ export type DeployContractResult<T> = SignDeployContractTxResult & { instance: T }
1279
+
1280
+ export abstract class ContractFactory<I, F extends Fields = Fields> {
1281
+ readonly contract: Contract
1282
+
1283
+ constructor(contract: Contract) {
1284
+ this.contract = contract
1285
+ }
1238
1286
 
1239
- export interface BuildExecuteScriptTx {
1240
- signerAddress: string
1241
- initialFields?: Fields
1287
+ abstract at(address: string): I
1288
+
1289
+ async deploy(signer: SignerProvider, deployParams: DeployContractParams<F>): Promise<DeployContractResult<I>> {
1290
+ const signerParams = await this.contract.txParamsForDeployment(signer, deployParams)
1291
+ const result = await signer.signAndSubmitDeployContractTx(signerParams)
1292
+ return {
1293
+ ...result,
1294
+ instance: this.at(result.contractAddress)
1295
+ }
1296
+ }
1297
+
1298
+ // This is used for testing contract functions
1299
+ stateForTest(initFields: F, asset?: Asset, address?: string): ContractState<F> {
1300
+ const newAsset = {
1301
+ alphAmount: asset?.alphAmount ?? ONE_ALPH,
1302
+ tokens: asset?.tokens
1303
+ }
1304
+ return this.contract.toState(initFields, newAsset, address)
1305
+ }
1306
+ }
1307
+
1308
+ export interface ExecuteScriptParams<P extends Fields = Fields> {
1309
+ initialFields: P
1242
1310
  attoAlphAmount?: Number256
1243
1311
  tokens?: Token[]
1244
1312
  gasAmount?: number
1245
1313
  gasPrice?: Number256
1246
1314
  }
1247
- assertType<Eq<keyof BuildExecuteScriptTx, keyof BuildTxParams<SignExecuteScriptTxParams>>>()
1248
1315
 
1249
- export interface BuildScriptTxResult {
1250
- fromGroup: number
1251
- toGroup: number
1316
+ export interface ExecuteScriptResult {
1317
+ groupIndex: number
1252
1318
  unsignedTx: string
1253
1319
  txId: string
1320
+ signature: string
1321
+ gasAmount: number
1322
+ gasPrice: Number256
1323
+ }
1324
+
1325
+ export interface CallContractParams<T extends Arguments = Arguments> {
1326
+ args: T
1327
+ worldStateBlockHash?: string
1328
+ txId?: string
1329
+ existingContracts?: string[]
1330
+ inputAssets?: node.TestInputAsset[]
1331
+ }
1332
+
1333
+ export interface CallContractResult<R> {
1334
+ returns: R
1335
+ gasUsed: number
1336
+ contracts: ContractState[]
1337
+ txInputs: string[]
1338
+ txOutputs: Output[]
1339
+ events: ContractEvent[]
1340
+ }
1341
+
1342
+ export type ContractCreatedEvent = ContractEvent<{ address: HexString }>
1343
+ export type ContractDestroyedEvent = ContractEvent<{ address: HexString }>
1344
+
1345
+ function decodeFields(event: node.ContractEvent, eventSig: EventSig, eventIndex: number): Fields {
1346
+ if (event.eventIndex !== eventIndex) {
1347
+ throw new Error(`Invalid event index: ${event.eventIndex}, expected: ${eventIndex}`)
1348
+ }
1349
+ return fromApiVals(event.fields, eventSig.fieldNames, eventSig.fieldTypes)
1350
+ }
1351
+
1352
+ export function decodeContractCreatedEvent(event: node.ContractEvent): Omit<ContractCreatedEvent, 'contractAddress'> {
1353
+ const fields = decodeFields(event, Contract.ContractCreatedEvent, Contract.ContractCreatedEventIndex)
1354
+ return {
1355
+ blockHash: event.blockHash,
1356
+ txId: event.txId,
1357
+ eventIndex: event.eventIndex,
1358
+ name: Contract.ContractCreatedEvent.name,
1359
+ fields: { address: fields['address'] as HexString }
1360
+ }
1361
+ }
1362
+
1363
+ export function decodeContractDestroyedEvent(
1364
+ event: node.ContractEvent
1365
+ ): Omit<ContractDestroyedEvent, 'contractAddress'> {
1366
+ const fields = decodeFields(event, Contract.ContractDestroyedEvent, Contract.ContractDestroyedEventIndex)
1367
+ return {
1368
+ blockHash: event.blockHash,
1369
+ txId: event.txId,
1370
+ eventIndex: event.eventIndex,
1371
+ name: Contract.ContractDestroyedEvent.name,
1372
+ fields: { address: fields['address'] as HexString }
1373
+ }
1374
+ }
1375
+
1376
+ export function subscribeEventsFromContract<T extends Fields, M extends ContractEvent<T>>(
1377
+ options: SubscribeOptions<M>,
1378
+ address: string,
1379
+ eventIndex: number,
1380
+ decodeFunc: (event: node.ContractEvent) => M,
1381
+ fromCount?: number
1382
+ ): EventSubscription {
1383
+ const messageCallback = (event: node.ContractEvent): Promise<void> => {
1384
+ if (event.eventIndex !== eventIndex) {
1385
+ return Promise.resolve()
1386
+ }
1387
+ return options.messageCallback(decodeFunc(event))
1388
+ }
1389
+
1390
+ const errorCallback = (err: any, subscription: Subscription<node.ContractEvent>): Promise<void> => {
1391
+ return options.errorCallback(err, subscription as unknown as Subscription<M>)
1392
+ }
1393
+ const opt: SubscribeOptions<node.ContractEvent> = {
1394
+ pollingInterval: options.pollingInterval,
1395
+ messageCallback: messageCallback,
1396
+ errorCallback: errorCallback
1397
+ }
1398
+ return subscribeToEvents(opt, address, fromCount)
1399
+ }
1400
+
1401
+ export async function testMethod<I, F extends Fields, A extends Arguments, R>(
1402
+ contract: ContractFactory<I, F>,
1403
+ methodName: string,
1404
+ params: Optional<TestContractParams<F, A>, 'testArgs' | 'initialFields'>
1405
+ ): Promise<TestContractResult<R>> {
1406
+ const txId = params?.txId ?? randomTxId()
1407
+ const apiParams = contract.contract.toApiTestContractParams(methodName, {
1408
+ ...params,
1409
+ txId: txId,
1410
+ initialFields: params.initialFields === undefined ? {} : params.initialFields,
1411
+ testArgs: params.testArgs === undefined ? {} : params.testArgs
1412
+ })
1413
+ const apiResult = await getCurrentNodeProvider().contracts.postContractsTestContract(apiParams)
1414
+ const testResult = contract.contract.fromApiTestContractResult(methodName, apiResult, txId)
1415
+ contract.contract.printDebugMessages(methodName, testResult.debugMessages)
1416
+ return testResult as TestContractResult<R>
1417
+ }
1418
+
1419
+ export abstract class ContractInstance {
1420
+ readonly address: Address
1421
+ readonly contractId: string
1422
+ readonly groupIndex: number
1423
+
1424
+ constructor(address: Address) {
1425
+ this.address = address
1426
+ this.contractId = binToHex(contractIdFromAddress(address))
1427
+ this.groupIndex = groupOfAddress(address)
1428
+ }
1429
+ }
1430
+
1431
+ export async function fetchContractState<F extends Fields, I extends ContractInstance>(
1432
+ contract: ContractFactory<I, F>,
1433
+ instance: ContractInstance
1434
+ ): Promise<ContractState<F>> {
1435
+ const contractState = await getCurrentNodeProvider().contracts.getContractsAddressState(instance.address, {
1436
+ group: instance.groupIndex
1437
+ })
1438
+ const state = contract.contract.fromApiContractState(contractState)
1439
+ return {
1440
+ ...state,
1441
+ fields: state.fields as F
1442
+ }
1443
+ }
1444
+
1445
+ export function subscribeContractCreatedEvent(
1446
+ instance: ContractInstance,
1447
+ options: SubscribeOptions<ContractCreatedEvent>,
1448
+ fromCount?: number
1449
+ ): EventSubscription {
1450
+ return subscribeEventsFromContract(
1451
+ options,
1452
+ instance.address,
1453
+ Contract.ContractCreatedEventIndex,
1454
+ (event) => {
1455
+ return {
1456
+ ...decodeContractCreatedEvent(event),
1457
+ contractAddress: instance.address
1458
+ }
1459
+ },
1460
+ fromCount
1461
+ )
1462
+ }
1463
+
1464
+ export function subscribeContractDestroyedEvent(
1465
+ instance: ContractInstance,
1466
+ options: SubscribeOptions<ContractDestroyedEvent>,
1467
+ fromCount?: number
1468
+ ): EventSubscription {
1469
+ return subscribeEventsFromContract(
1470
+ options,
1471
+ instance.address,
1472
+ Contract.ContractDestroyedEventIndex,
1473
+ (event) => {
1474
+ return {
1475
+ ...decodeContractDestroyedEvent(event),
1476
+ contractAddress: instance.address
1477
+ }
1478
+ },
1479
+ fromCount
1480
+ )
1481
+ }
1482
+
1483
+ export function decodeEvent<F extends Fields, M extends ContractEvent<F>>(
1484
+ contract: Contract,
1485
+ instance: ContractInstance,
1486
+ event: node.ContractEvent,
1487
+ targetEventIndex: number
1488
+ ): M {
1489
+ if (
1490
+ event.eventIndex !== targetEventIndex &&
1491
+ !(targetEventIndex >= 0 && targetEventIndex < contract.eventsSig.length)
1492
+ ) {
1493
+ throw new Error('Invalid event index: ' + event.eventIndex + ', expected: ' + targetEventIndex)
1494
+ }
1495
+ const eventSig = contract.eventsSig[`${targetEventIndex}`]
1496
+ const fieldNames = eventSig.fieldNames
1497
+ const fieldTypes = eventSig.fieldTypes
1498
+ const fields = fromApiVals(event.fields, fieldNames, fieldTypes)
1499
+ return {
1500
+ contractAddress: instance.address,
1501
+ blockHash: event.blockHash,
1502
+ txId: event.txId,
1503
+ eventIndex: event.eventIndex,
1504
+ name: eventSig.name,
1505
+ fields: fields
1506
+ } as M
1507
+ }
1508
+
1509
+ export function subscribeContractEvent<F extends Fields, M extends ContractEvent<F>>(
1510
+ contract: Contract,
1511
+ instance: ContractInstance,
1512
+ options: SubscribeOptions<M>,
1513
+ eventName: string,
1514
+ fromCount?: number
1515
+ ): EventSubscription {
1516
+ const eventIndex = contract.eventsSig.findIndex((sig) => sig.name === eventName)
1517
+ return subscribeEventsFromContract<F, M>(
1518
+ options,
1519
+ instance.address,
1520
+ eventIndex,
1521
+ (event) => decodeEvent(contract, instance, event, eventIndex),
1522
+ fromCount
1523
+ )
1524
+ }
1525
+
1526
+ export function subscribeAllEvents(
1527
+ contract: Contract,
1528
+ instance: ContractInstance,
1529
+ options: SubscribeOptions<ContractEvent<any>>,
1530
+ fromCount?: number
1531
+ ): EventSubscription {
1532
+ const messageCallback = (event: node.ContractEvent): Promise<void> => {
1533
+ switch (event.eventIndex) {
1534
+ case Contract.ContractCreatedEventIndex: {
1535
+ return options.messageCallback({
1536
+ ...decodeContractCreatedEvent(event),
1537
+ contractAddress: instance.address
1538
+ })
1539
+ }
1540
+
1541
+ case Contract.ContractDestroyedEventIndex: {
1542
+ return options.messageCallback({
1543
+ ...decodeContractDestroyedEvent(event),
1544
+ contractAddress: instance.address
1545
+ })
1546
+ }
1547
+
1548
+ default:
1549
+ return options.messageCallback({
1550
+ ...decodeEvent(contract, instance, event, event.eventIndex),
1551
+ contractAddress: instance.address
1552
+ })
1553
+ }
1554
+ }
1555
+ const errorCallback = (err: any, subscription: Subscription<node.ContractEvent>): Promise<void> => {
1556
+ return options.errorCallback(err, subscription as unknown as Subscription<ContractEvent<any>>)
1557
+ }
1558
+ const opt: SubscribeOptions<node.ContractEvent> = {
1559
+ pollingInterval: options.pollingInterval,
1560
+ messageCallback: messageCallback,
1561
+ errorCallback: errorCallback
1562
+ }
1563
+ return subscribeToEvents(opt, instance.address, fromCount)
1564
+ }
1565
+
1566
+ export async function callMethod<I, F extends Fields, A extends Arguments, R>(
1567
+ contract: ContractFactory<I, F>,
1568
+ instance: ContractInstance,
1569
+ methodName: string,
1570
+ params: Optional<CallContractParams<A>, 'args'>
1571
+ ): Promise<CallContractResult<R>> {
1572
+ const methodIndex = contract.contract.getMethodIndex(methodName)
1573
+ const txId = params?.txId ?? randomTxId()
1574
+ const callParams = contract.contract.toApiCallContract(
1575
+ { ...params, txId: txId, args: params.args === undefined ? {} : params.args },
1576
+ instance.groupIndex,
1577
+ instance.address,
1578
+ methodIndex
1579
+ )
1580
+ const result = await getCurrentNodeProvider().contracts.postContractsCallContract(callParams)
1581
+ const callResult = contract.contract.fromApiCallContractResult(result, txId, methodIndex)
1582
+ return callResult as CallContractResult<R>
1254
1583
  }