This repository has been archived by the owner on Jun 30, 2022. It is now read-only.
/
ContractImplementation.js
145 lines (126 loc) · 4.57 KB
/
ContractImplementation.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import contract from 'truffle-contract'
import _ from 'lodash'
import isRequired from '../utils/isRequired'
import * as errorConstants from '../constants/error'
import Web3Wrapper from '../utils/Web3Wrapper'
/**
* ContractImplementation is a parent class for on chain contracts. It loads the contract from the
* blockchain and exposes the contract instance for use by the child.
*/
class ContractImplementation {
constructor(
web3Provider = isRequired('web3Provider'),
artifact = isRequired('artifact'),
contractAddress
) {
this.contractAddress = contractAddress
this.artifact = artifact
this.contractInstance = null
this._Web3Wrapper = new Web3Wrapper(web3Provider)
// loading params
this._contractLoadedResolver = null
this._contractLoadedRejecter = null
this._loadingContractInstance = null
this.isLoading = false
}
/**
* Load contract instance if not yet initialized. Returns loading promise
* @returns {Promise} resolves to contractInstance
*/
loadContract = async () => {
if (this.isLoading) return this._loadingContractInstance
if (this.contractInstance) return this.contractInstance
if (!this.contractAddress || !this.artifact)
throw new Error(errorConstants.CONTRACT_INSTANCE_NOT_SET)
const newLoadingPromise = this._newLoadingPromise()
this._loadingContractInstance = newLoadingPromise
this._load()
return newLoadingPromise
}
/**
* Set a new contract instance
* @param {string} contractAddress - The address of the contract
* @param {object} artifact - Contract artifact to use to load contract
* @returns {object} contractInstance object
*/
setContractInstance = async (
contractAddress = this.contractAddress,
artifact = this.artifact
) => {
this.contractAddress = contractAddress
this.artifact = artifact
this.contractInstance = null
return this.loadContract()
}
/**
* Load an existing contract from the current artifact and address
*/
_load = async () => {
this.isLoading = true
try {
this.contractInstance = await this._instantiateContractIfExistsAsync(
this.artifact,
this.contractAddress
)
this.isLoading = false
this._contractLoadedResolver(this.contractInstance)
} catch (err) {
this.isLoading = false
this._contractLoadedRejecter(err)
}
}
/**
* Instantiate contract.
* @private
* @param {object} artifact - The contract artifact.
* @param {string} address - The hex encoded contract Ethereum address
* @returns {object} - The contract instance.
*/
_instantiateContractIfExistsAsync = async (artifact, address) => {
try {
const c = await contract(artifact)
await c.setProvider(await this._Web3Wrapper.getProvider())
const contractInstance = _.isUndefined(address)
? await c.deployed()
: await c.at(address)
// Estimate gas before sending transactions
for (const funcABI of contractInstance.abi) {
// Check for non-constant functions
if (funcABI.type === 'function' && funcABI.constant === false) {
const func = contractInstance[funcABI.name]
// eslint-disable-next-line no-loop-func
contractInstance[funcABI.name] = async (...args) => {
await func.estimateGas(...args) // Estimate gas (also checks for possible failures)
return func(...args) // Call original function
}
// Keep reference to the original function for special cases
contractInstance[funcABI.name].original = func
// Forward other accessors to the original function
Object.setPrototypeOf(contractInstance[funcABI.name], func)
}
}
return contractInstance
} catch (err) {
console.error(err)
if (_.includes(err.message, 'not been deployed to detected network'))
throw new Error(errorConstants.CONTRACT_NOT_DEPLOYED)
throw new Error(errorConstants.UNABLE_TO_LOAD_CONTRACT)
}
}
/**
* Create a new Promise to be used in loading the contract.
* @returns {Promise} - Resolves to contract instance.
*/
_newLoadingPromise = () =>
new Promise((resolve, reject) => {
this._contractLoadedResolver = resolve
this._contractLoadedRejecter = reject
})
// we have getters so that abstract classes can provide public access to implementations variables
/**
* Get the contract address for the currently instantiated contract.
* @returns {string} - The address of the contract.
*/
getContractAddress = () => this.contractAddress
}
export default ContractImplementation