1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | import Compiler from '../../es/contract/compiler'
|
18 | import { describe, it, before } from 'mocha'
|
19 | import { BaseAe, getSdk, compilerUrl, publicKey } from './'
|
20 | import { decode } from '../../es/tx/builder/helpers'
|
21 | import * as R from 'ramda'
|
22 | import { randomName } from '../utils'
|
23 | import { decodeEvents, readType, SOPHIA_TYPES } from '../../es/contract/aci/transformation'
|
24 | import { hash, personalMessageToBinary } from '../../es/utils/crypto'
|
25 | import { getFunctionACI } from '../../es/contract/aci/helpers'
|
26 |
|
27 | const identityContract = `
|
28 | contract Identity =
|
29 | entrypoint main(x : int) = x
|
30 | `
|
31 |
|
32 | const errorContract = `
|
33 | contract Identity =
|
34 | payable stateful entrypoint main(x : address) = Chain.spend(x, 1000000000)
|
35 | `
|
36 |
|
37 | const stateContract = `
|
38 | contract StateContract =
|
39 | record state = { value: string }
|
40 | entrypoint init(value) : state = { value = value }
|
41 | entrypoint retrieve() : string = state.value
|
42 | `
|
43 | const libContract = `
|
44 | namespace TestLib =
|
45 | function sum(x: int, y: int) : int = x + y
|
46 | `
|
47 | const contractWithLib = `
|
48 | include "testLib"
|
49 | contract Voting =
|
50 | entrypoint sumNumbers(x: int, y: int) : int = TestLib.sum(x, y)
|
51 | `
|
52 | const testContract = `
|
53 | namespace Test =
|
54 | function double(x: int): int = x*2
|
55 |
|
56 |
|
57 | contract Voting =
|
58 | type test_type = int
|
59 | record state = { value: string, key: test_type, testOption: option(string) }
|
60 | record test_record = { value: string, key: list(test_type) }
|
61 | entrypoint test : () => int
|
62 |
|
63 | include "testLib"
|
64 | contract StateContract =
|
65 | type number = int
|
66 | record state = { value: string, key: number, testOption: option(string) }
|
67 | record yesEr = { t: number}
|
68 |
|
69 | datatype event = TheFirstEvent(int) | AnotherEvent(string, address) | AnotherEvent2(bool, string, int)
|
70 | datatype dateUnit = Year | Month | Day
|
71 | datatype one_or_both('a, 'b) = Left('a) | Right('b) | Both('a, 'b)
|
72 |
|
73 | entrypoint init(value: string, key: int, testOption: option(string)) : state = { value = value, key = key, testOption = testOption }
|
74 | entrypoint retrieve() : string*int = (state.value, state.key)
|
75 |
|
76 | entrypoint remoteContract(a: Voting) : int = 1
|
77 | entrypoint remoteArgs(a: Voting.test_record) : Voting.test_type = 1
|
78 | entrypoint intFn(a: int) : int = a
|
79 | payable entrypoint stringFn(a: string) : string = a
|
80 | entrypoint boolFn(a: bool) : bool = a
|
81 | entrypoint addressFn(a: address) : address = a
|
82 | entrypoint contractAddress (ct: address) : address = ct
|
83 | entrypoint accountAddress (ak: address) : address = ak
|
84 |
|
85 | entrypoint tupleFn (a: string*int) : string*int = a
|
86 | entrypoint tupleInTupleFn (a: (string*string)*int) : (string*string)*int = a
|
87 | entrypoint tupleWithList (a: list(int)*int) : list(int)*int = a
|
88 |
|
89 | entrypoint listFn(a: list(int)) : list(int) = a
|
90 | entrypoint listInListFn(a: list(list(int))) : list(list(int)) = a
|
91 |
|
92 | entrypoint mapFn(a: map(address, string*int)) : map(address, string*int) = a
|
93 | entrypoint mapOptionFn(a: map(address, string*option(int))) : map(address, string*option(int)) = a
|
94 |
|
95 | entrypoint getRecord() : state = state
|
96 | stateful entrypoint setRecord(s: state) = put(s)
|
97 |
|
98 | entrypoint intOption(s: option(int)) : option(int) = s
|
99 | entrypoint listOption(s: option(list(int*string))) : option(list(int*string)) = s
|
100 |
|
101 | entrypoint testFn(a: list(int), b: bool) : list(int)*bool = (a, b)
|
102 | entrypoint approve(tx_id: int, voting_contract: Voting) : int = tx_id
|
103 |
|
104 | entrypoint hashFn(s: hash): hash = s
|
105 | entrypoint signatureFn(s: signature): signature = s
|
106 | entrypoint bytesFn(s: bytes(32)): bytes(32) = s
|
107 |
|
108 | entrypoint usingExternalLib(s: int): int = Test.double(s)
|
109 |
|
110 | entrypoint datTypeFn(s: dateUnit): dateUnit = s
|
111 | entrypoint datTypeGFn(x : one_or_both(int, string)) : int =
|
112 | switch(x)
|
113 | Left(x) => x
|
114 | Right(_) => abort("asdasd")
|
115 | Both(x, _) => x
|
116 | stateful entrypoint emitEvents() : unit =
|
117 | Chain.event(TheFirstEvent(42))
|
118 | Chain.event(AnotherEvent("This is not indexed", Contract.address))
|
119 | Chain.event(AnotherEvent2(true, "This is not indexed", 1))
|
120 | `
|
121 | const aensDelegationContract = `
|
122 | contract DelegateTest =
|
123 | // Transactions
|
124 | stateful payable entrypoint signedPreclaim(addr : address,
|
125 | chash : hash,
|
126 | sign : signature) : unit =
|
127 | AENS.preclaim(addr, chash, signature = sign)
|
128 | stateful entrypoint signedClaim(addr : address,
|
129 | name : string,
|
130 | salt : int,
|
131 | name_fee : int,
|
132 | sign : signature) : unit =
|
133 | AENS.claim(addr, name, salt, name_fee, signature = sign)
|
134 | stateful entrypoint signedTransfer(owner : address,
|
135 | new_owner : address,
|
136 | name : string,
|
137 | sign : signature) : unit =
|
138 | AENS.transfer(owner, new_owner, name, signature = sign)
|
139 | stateful entrypoint signedRevoke(owner : address,
|
140 | name : string,
|
141 | sign : signature) : unit =
|
142 | AENS.revoke(owner, name, signature = sign)`
|
143 | const oracleContract = `
|
144 | contract DelegateTest =
|
145 | type fee = int
|
146 | type ttl = Chain.ttl
|
147 | stateful payable entrypoint signedRegisterOracle(acct : address,
|
148 | sign : signature,
|
149 | qfee : fee,
|
150 | ttl : ttl) : oracle(string, string) =
|
151 | Oracle.register(acct, qfee, ttl, signature = sign)
|
152 | stateful payable entrypoint signedExtendOracle(o : oracle(string, string),
|
153 | sign : signature, // Signed oracle address
|
154 | ttl : ttl) : unit =
|
155 | Oracle.extend(o, signature = sign, ttl)
|
156 |
|
157 | payable stateful entrypoint createQuery(o : oracle(string, string),
|
158 | q : string,
|
159 | qfee : int,
|
160 | qttl : Chain.ttl,
|
161 | rttl : Chain.ttl) : oracle_query(string, string) =
|
162 | require(qfee =< Call.value, "insufficient value for qfee")
|
163 | require(Oracle.check(o), "oracle not valid")
|
164 | Oracle.query(o, q, qfee, qttl, rttl)
|
165 |
|
166 | entrypoint queryFee(o : oracle(string, int)) : int =
|
167 | Oracle.query_fee(o)
|
168 |
|
169 | stateful entrypoint respond(o : oracle(string, string),
|
170 | q : oracle_query(string, string),
|
171 | sign : signature, // Signed oracle query id + contract address
|
172 | r : string) =
|
173 | Oracle.respond(o, q, signature = sign, r)`
|
174 | const encodedNumberSix = 'cb_DA6sWJo='
|
175 | const signSource = `
|
176 | contract Sign =
|
177 | entrypoint verify (msg: hash, pub: address, sig: signature): bool =
|
178 | Crypto.verify_sig(msg, pub, sig)
|
179 | `
|
180 | const filesystem = {
|
181 | testLib: libContract
|
182 | }
|
183 |
|
184 | describe('Contract', function () {
|
185 | let contract
|
186 | let bytecode
|
187 | let deployed
|
188 |
|
189 | before(async function () {
|
190 | contract = await getSdk()
|
191 | })
|
192 | describe('Aens and Oracle operation delegation', () => {
|
193 | let cInstance
|
194 | let cInstanceOracle
|
195 | before(async () => {
|
196 | cInstance = await contract.getContractInstance(aensDelegationContract)
|
197 | cInstanceOracle = await contract.getContractInstance(oracleContract)
|
198 | await cInstance.deploy()
|
199 | await cInstanceOracle.deploy()
|
200 | })
|
201 | it('Delegate AENS operations', async () => {
|
202 | const name = randomName(15)
|
203 | const contractAddress = cInstance.deployInfo.address
|
204 | const nameFee = 20 * (10 ** 18)
|
205 | const current = await contract.address()
|
206 |
|
207 |
|
208 | const { salt: _salt } = await contract.aensPreclaim(name)
|
209 |
|
210 |
|
211 | const preclaimSig = await contract.delegateNamePreclaimSignature(contractAddress)
|
212 | console.log(`preclaimSig -> ${preclaimSig}`)
|
213 |
|
214 |
|
215 | await contract.awaitHeight((await contract.height()) + 2)
|
216 |
|
217 | const claimSig = await contract.delegateNameClaimSignature(contractAddress, name)
|
218 | const claim = await cInstance.methods.signedClaim(await contract.address(), name, _salt, nameFee, claimSig)
|
219 | claim.result.returnType.should.be.equal('ok')
|
220 | await contract.awaitHeight((await contract.height()) + 2)
|
221 |
|
222 |
|
223 | const transferSig = await contract.delegateNameTransferSignature(contractAddress, name)
|
224 | const onAccount = contract.addresses().find(acc => acc !== current)
|
225 | const transfer = await cInstance.methods.signedTransfer(await contract.address(), onAccount, name, transferSig)
|
226 | transfer.result.returnType.should.be.equal('ok')
|
227 |
|
228 | await contract.awaitHeight((await contract.height()) + 2)
|
229 |
|
230 | const revokeSig = await contract.delegateNameRevokeSignature(contractAddress, name, { onAccount })
|
231 | const revoke = await cInstance.methods.signedRevoke(onAccount, name, revokeSig)
|
232 | revoke.result.returnType.should.be.equal('ok')
|
233 |
|
234 | try {
|
235 | await contract.aensQuery(name)
|
236 | } catch (e) {
|
237 | e.message.should.be.an('string')
|
238 | }
|
239 | })
|
240 | it('Delegate Oracle operations', async () => {
|
241 | const contractAddress = cInstanceOracle.deployInfo.address
|
242 | const current = await contract.address()
|
243 | const onAccount = contract.addresses().find(acc => acc !== current)
|
244 | const qFee = 500000
|
245 | const ttl = 'RelativeTTL(50)'
|
246 | const oracleId = `ok_${onAccount.slice(3)}`
|
247 |
|
248 | const oracleCreateSig = await contract.delegateOracleRegisterSignature(contractAddress, { onAccount })
|
249 | const oracleRegister = await cInstanceOracle.methods.signedRegisterOracle(onAccount, oracleCreateSig, qFee, ttl, { onAccount })
|
250 | oracleRegister.result.returnType.should.be.equal('ok')
|
251 | const oracle = await contract.getOracleObject(oracleId)
|
252 | oracle.id.should.be.equal(oracleId)
|
253 |
|
254 | const oracleExtendSig = await contract.delegateOracleExtendSignature(contractAddress, { onAccount })
|
255 | const queryExtend = await cInstanceOracle.methods.signedExtendOracle(oracleId, oracleExtendSig, ttl, { onAccount })
|
256 | queryExtend.result.returnType.should.be.equal('ok')
|
257 | const oracleExtended = await contract.getOracleObject(oracleId)
|
258 | console.log(oracleExtended)
|
259 | oracleExtended.ttl.should.be.equal(oracle.ttl + 50)
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 | })
|
281 | })
|
282 | it('precompiled bytecode can be deployed', async () => {
|
283 | const { version, consensusProtocolVersion } = contract.getNodeInfo()
|
284 | console.log(`Node => ${version}, consensus => ${consensusProtocolVersion}, compiler => ${contract.compilerVersion}`)
|
285 | const code = await contract.contractCompile(identityContract)
|
286 | return contract.contractDeploy(code.bytecode, identityContract).should.eventually.have.property('address')
|
287 | })
|
288 | it('Verify message in Sophia', async () => {
|
289 | const msg = personalMessageToBinary('Hello')
|
290 | const msgHash = hash(msg)
|
291 | const signature = await contract.sign(msgHash)
|
292 | const signContract = await contract.getContractInstance(signSource)
|
293 | await signContract.deploy()
|
294 | const { decodedResult } = await signContract.methods.verify(msgHash, await contract.address(), signature)
|
295 | decodedResult.should.be.equal(true)
|
296 | })
|
297 | it('compiles Sophia code', async () => {
|
298 | bytecode = await contract.contractCompile(identityContract)
|
299 | return bytecode.should.have.property('bytecode')
|
300 | })
|
301 |
|
302 | it('deploy static compiled contract', async () => {
|
303 | const res = await bytecode.deployStatic([])
|
304 | res.result.should.have.property('gasUsed')
|
305 | res.result.should.have.property('returnType')
|
306 | })
|
307 |
|
308 | it('deploys compiled contracts', async () => {
|
309 | deployed = await bytecode.deploy([])
|
310 | return deployed.should.have.property('address')
|
311 | })
|
312 |
|
313 | it('Deploy/Call/Dry-run contract using callData', async () => {
|
314 | const callArg = 1
|
315 | const { bytecode } = await contract.contractCompile(identityContract)
|
316 | const callDataDeploy = await contract.contractEncodeCall(identityContract, 'init', [])
|
317 | const callDataCall = await contract.contractEncodeCall(identityContract, 'main', [callArg.toString()])
|
318 |
|
319 | const deployStatic = await contract.contractCallStatic(identityContract, null, 'init', callDataDeploy, { bytecode })
|
320 | deployStatic.result.should.have.property('gasUsed')
|
321 | deployStatic.result.should.have.property('returnType')
|
322 |
|
323 | const deployed = await contract.contractDeploy(bytecode, identityContract, callDataDeploy)
|
324 | deployed.result.should.have.property('gasUsed')
|
325 | deployed.result.should.have.property('returnType')
|
326 | deployed.should.have.property('address')
|
327 |
|
328 | const callStaticRes = await contract.contractCallStatic(identityContract, deployed.address, 'main', callDataCall)
|
329 | callStaticRes.result.should.have.property('gasUsed')
|
330 | callStaticRes.result.should.have.property('returnType')
|
331 | const decodedCallStaticResult = await callStaticRes.decode()
|
332 | decodedCallStaticResult.should.be.equal(callArg)
|
333 |
|
334 | const callRes = await contract.contractCall(identityContract, deployed.address, 'main', callDataCall)
|
335 | callRes.result.should.have.property('gasUsed')
|
336 | callRes.result.should.have.property('returnType')
|
337 | callRes.result.should.have.property('returnType')
|
338 | const decodedCallResult = await callRes.decode()
|
339 | decodedCallResult.should.be.equal(callArg)
|
340 | })
|
341 |
|
342 | it('Deploy and call contract on specific account', async () => {
|
343 | const current = await contract.address()
|
344 | const onAccount = contract.addresses().find(acc => acc !== current)
|
345 |
|
346 | const deployed = await bytecode.deploy([], { onAccount })
|
347 | deployed.result.callerId.should.be.equal(onAccount)
|
348 | const callRes = await deployed.call('main', ['42'])
|
349 | callRes.result.callerId.should.be.equal(onAccount)
|
350 | const callStaticRes = await deployed.callStatic('main', ['42'])
|
351 | callStaticRes.result.callerId.should.be.equal(onAccount)
|
352 | })
|
353 |
|
354 | it('Call-Static deploy transaction', async () => {
|
355 | const compiled = bytecode.bytecode
|
356 | const res = await contract.contractCallStatic(identityContract, null, 'init', [], { bytecode: compiled })
|
357 | res.result.should.have.property('gasUsed')
|
358 | res.result.should.have.property('returnType')
|
359 | })
|
360 |
|
361 | it('Call-Static deploy transaction on specific hash', async () => {
|
362 | const { hash } = await contract.topBlock()
|
363 | const compiled = bytecode.bytecode
|
364 | const res = await contract.contractCallStatic(identityContract, null, 'init', [], { bytecode: compiled, top: hash })
|
365 | res.result.should.have.property('gasUsed')
|
366 | res.result.should.have.property('returnType')
|
367 | })
|
368 | it('Test handleError(Parse and check contract execution error)', async () => {
|
369 | const code = await contract.contractCompile(errorContract)
|
370 | const deployed = await code.deploy()
|
371 | try {
|
372 | await deployed.call('main', [await contract.address()])
|
373 | } catch (e) {
|
374 | e.message.indexOf('Invocation failed').should.not.be.equal(-1)
|
375 | }
|
376 | })
|
377 |
|
378 | it('Dry-run without accounts', async () => {
|
379 | const client = await BaseAe()
|
380 | client.removeAccount(publicKey)
|
381 | client.addresses().length.should.be.equal(0)
|
382 | const address = await client.address().catch(e => false)
|
383 | address.should.be.equal(false)
|
384 | const { result } = await client.contractCallStatic(identityContract, deployed.address, 'main', ['42'])
|
385 | result.callerId.should.be.equal(client.Ae.defaults.dryRunAccount.pub)
|
386 | })
|
387 |
|
388 | it('calls deployed contracts', async () => {
|
389 | const result = await deployed.call('main', ['42'])
|
390 | return result.decode().should.eventually.become(42)
|
391 | })
|
392 |
|
393 | it('call contract/deploy with `waitMined: false`', async () => {
|
394 | const deployed = await bytecode.deploy([], { waitMined: false })
|
395 | await contract.poll(deployed.transaction)
|
396 | Boolean(deployed.result === undefined).should.be.equal(true)
|
397 | Boolean(deployed.txData === undefined).should.be.equal(true)
|
398 | const result = await deployed.call('main', ['42'], { waitMined: false, verify: false })
|
399 | Boolean(result.result === undefined).should.be.equal(true)
|
400 | Boolean(result.txData === undefined).should.be.equal(true)
|
401 | await contract.poll(result.hash)
|
402 | })
|
403 |
|
404 | it('calls deployed contracts static', async () => {
|
405 | const result = await deployed.callStatic('main', ['42'])
|
406 | return result.decode().should.eventually.become(42)
|
407 | })
|
408 |
|
409 | it('initializes contract state', async () => {
|
410 | const data = '"Hello World!"'
|
411 | return contract.contractCompile(stateContract)
|
412 | .then(bytecode => bytecode.deploy([data]))
|
413 | .then(deployed => deployed.call('retrieve'))
|
414 | .then(result => result.decode())
|
415 | .catch(e => {
|
416 | console.log(e)
|
417 | throw e
|
418 | })
|
419 | .should.eventually.become('Hello World!')
|
420 | })
|
421 | describe('Namespaces', () => {
|
422 | let deployed
|
423 | it('Can compiler contract with external deps', async () => {
|
424 | const filesystem = {
|
425 | testLib: libContract
|
426 | }
|
427 | const compiled = await contract.contractCompile(contractWithLib, { filesystem })
|
428 | compiled.should.have.property('bytecode')
|
429 | })
|
430 | it('Throw error when try to compile contract without providing external deps', async () => {
|
431 | try {
|
432 | await contract.contractCompile(contractWithLib)
|
433 | } catch (e) {
|
434 | e.message.indexOf('Couldn\'t find include file').should.not.be.equal(-1)
|
435 | }
|
436 | })
|
437 | it('Can deploy contract with external deps', async () => {
|
438 | const filesystem = {
|
439 | testLib: libContract
|
440 | }
|
441 | const compiled = await contract.contractCompile(contractWithLib, { filesystem })
|
442 | deployed = await compiled.deploy()
|
443 | deployed.should.have.property('address')
|
444 |
|
445 | const deployedStatic = await compiled.deployStatic([])
|
446 | deployedStatic.result.should.have.property('gasUsed')
|
447 | deployedStatic.result.should.have.property('returnType')
|
448 |
|
449 | const encodedCallData = await compiled.encodeCall('sumNumbers', ['1', '2'])
|
450 | encodedCallData.indexOf('cb_').should.not.be.equal(-1)
|
451 | })
|
452 | it('Can call contract with external deps', async () => {
|
453 | const callResult = await deployed.call('sumNumbers', ['1', '2'])
|
454 | const decoded = await callResult.decode()
|
455 | decoded.should.be.equal(3)
|
456 |
|
457 | const callStaticResult = await deployed.callStatic('sumNumbers', ['1', '2'])
|
458 | const decoded2 = await callStaticResult.decode()
|
459 | decoded2.should.be.equal(3)
|
460 | })
|
461 | })
|
462 |
|
463 | describe('Sophia Compiler', function () {
|
464 | let callData
|
465 | let bytecode
|
466 | it('Init un-compatible compiler version', async () => {
|
467 | try {
|
468 |
|
469 | const compiler = await Compiler({ compilerUrl })
|
470 |
|
471 | compiler.compilerVersion = '1.0.0'
|
472 | await compiler.checkCompatibility()
|
473 | } catch (e) {
|
474 | e.message.indexOf('Unsupported compiler version 1.0.0').should.not.be.equal(-1)
|
475 | }
|
476 | })
|
477 | it('compile', async () => {
|
478 | bytecode = await contract.compileContractAPI(identityContract)
|
479 | const prefix = bytecode.slice(0, 2)
|
480 | const isString = typeof bytecode === 'string'
|
481 | prefix.should.be.equal('cb')
|
482 | isString.should.be.equal(true)
|
483 | })
|
484 | it('Get FATE assembler', async () => {
|
485 | const result = await contract.getFateAssembler(bytecode)
|
486 | result.should.be.a('object')
|
487 | const assembler = result['fate-assembler']
|
488 | assembler.should.be.a('string')
|
489 | })
|
490 | it('Get compiler version from bytecode', async () => {
|
491 | const version = await contract.getBytecodeCompilerVersion(bytecode)
|
492 | console.log(version)
|
493 | })
|
494 | it('get contract ACI', async () => {
|
495 | const aci = await contract.contractGetACI(identityContract)
|
496 | aci.should.have.property('interface')
|
497 | })
|
498 | it('encode call-data', async () => {
|
499 | callData = await contract.contractEncodeCallDataAPI(identityContract, 'init', [])
|
500 | const prefix = callData.slice(0, 2)
|
501 | const isString = typeof callData === 'string'
|
502 | prefix.should.be.equal('cb')
|
503 | isString.should.be.equal(true)
|
504 | })
|
505 | it('decode call result', async () => {
|
506 | return contract.contractDecodeCallResultAPI(identityContract, 'main', encodedNumberSix, 'ok', { backend: 'fate' }).should.eventually.become(6)
|
507 | })
|
508 | it('Decode call-data using source', async () => {
|
509 | const decodedCallData = await contract.contractDecodeCallDataBySourceAPI(identityContract, 'init', callData)
|
510 | decodedCallData.arguments.should.be.an('array')
|
511 | decodedCallData.arguments.length.should.be.equal(0)
|
512 | decodedCallData.function.should.be.equal('init')
|
513 | })
|
514 | it('Decode call-data using bytecode', async () => {
|
515 | const decodedCallData = await contract.contractDecodeCallDataByCodeAPI(bytecode, callData)
|
516 | decodedCallData.arguments.should.be.an('array')
|
517 | decodedCallData.arguments.length.should.be.equal(0)
|
518 | decodedCallData.function.should.be.equal('init')
|
519 | })
|
520 | it('Decode data API', async () => {
|
521 | const returnData = 'cb_bzvA9Af6'
|
522 | return contract.contractDecodeDataAPI('string', returnData).catch(e => 1).should.eventually.become(1)
|
523 | })
|
524 | it('validate bytecode', async () => {
|
525 | return contract.validateByteCodeAPI(bytecode, identityContract).should.eventually.become(true)
|
526 | })
|
527 | it('Use invalid compiler url', async () => {
|
528 | try {
|
529 | const cloned = R.clone(contract)
|
530 | await cloned.setCompilerUrl('https://compiler.aepps.comas')
|
531 | } catch (e) {
|
532 | e.message.should.be.equal('Compiler do not respond')
|
533 | }
|
534 | })
|
535 | })
|
536 |
|
537 | describe('Contract ACI Interface', function () {
|
538 | let contractObject
|
539 | describe('Events parsing', async () => {
|
540 | let cInstance
|
541 | let eventResult
|
542 | let decodedEventsWithoutACI
|
543 | let decodedEventsUsingACI
|
544 | let decodedEventsUsingBuildInMethod
|
545 |
|
546 | before(async () => {
|
547 | cInstance = await contract.getContractInstance(testContract, { filesystem })
|
548 | await cInstance.deploy(['test', 1, 'some'])
|
549 | eventResult = await cInstance.methods.emitEvents()
|
550 | const { log } = await contract.tx(eventResult.hash)
|
551 | decodedEventsWithoutACI = decodeEvents(log, { schema: events })
|
552 | decodedEventsUsingACI = cInstance.decodeEvents('emitEvents', log)
|
553 | decodedEventsUsingBuildInMethod = cInstance.methods.emitEvents.decodeEvents(log)
|
554 | })
|
555 | const events = [
|
556 | { name: 'AnotherEvent2', types: [SOPHIA_TYPES.bool, SOPHIA_TYPES.string, SOPHIA_TYPES.int] },
|
557 | { name: 'AnotherEvent', types: [SOPHIA_TYPES.string, SOPHIA_TYPES.address] },
|
558 | { name: 'TheFirstEvent', types: [SOPHIA_TYPES.int] }
|
559 | ]
|
560 | const checkEvents = (event, schema) => {
|
561 | schema.name.should.be.equal(event.name)
|
562 | schema.types.forEach((t, tIndex) => {
|
563 | const value = event.decoded[tIndex]
|
564 | const isNumber = typeof value === 'string' || typeof value === 'number'
|
565 | const v = typeof value === t
|
566 | switch (t) {
|
567 | case SOPHIA_TYPES.address:
|
568 | event.address.should.be.equal(`ct_${value}`)
|
569 | break
|
570 | case SOPHIA_TYPES.int:
|
571 | isNumber.should.be.equal(true)
|
572 | Number.isInteger(+value).should.be.equal(true)
|
573 | break
|
574 | case SOPHIA_TYPES.bool:
|
575 | value.should.be.a('boolean')
|
576 | break
|
577 | default:
|
578 | v.should.be.equal(true)
|
579 | break
|
580 | }
|
581 | })
|
582 | }
|
583 | events
|
584 | .forEach((el, i) => {
|
585 | describe(`Correct parse of ${el.name}(${el.types})`, () => {
|
586 | it('ACI call result', () => checkEvents(eventResult.decodedEvents[i], el))
|
587 | it('ACI instance', () => checkEvents(decodedEventsUsingACI[i], el))
|
588 | it('ACI instance methods', () => checkEvents(decodedEventsUsingBuildInMethod[i], el))
|
589 | it('Without ACI', () => checkEvents(decodedEventsWithoutACI[i], el))
|
590 | })
|
591 | })
|
592 | })
|
593 |
|
594 | it('Generate ACI object', async () => {
|
595 | contractObject = await contract.getContractInstance(testContract, { filesystem, opt: { ttl: 0 } })
|
596 | contractObject.should.have.property('interface')
|
597 | contractObject.should.have.property('aci')
|
598 | contractObject.should.have.property('source')
|
599 | contractObject.should.have.property('compiled')
|
600 | contractObject.should.have.property('deployInfo')
|
601 | contractObject.should.have.property('compile')
|
602 | contractObject.should.have.property('call')
|
603 | contractObject.should.have.property('deploy')
|
604 | contractObject.options.ttl.should.be.equal(0)
|
605 | contractObject.options.should.have.property('filesystem')
|
606 | contractObject.options.filesystem.should.have.property('testLib')
|
607 | const functionsFromACI = contractObject.aci.functions.map(({ name }) => name)
|
608 | const methods = Object.keys(contractObject.methods)
|
609 | R.equals(methods, functionsFromACI).should.be.equal(true)
|
610 | })
|
611 | it('Compile contract', async () => {
|
612 | await contractObject.compile()
|
613 | const isCompiled = contractObject.compiled.length && contractObject.compiled.slice(0, 3) === 'cb_'
|
614 | isCompiled.should.be.equal(true)
|
615 | })
|
616 | it('Dry-run deploy fn', async () => {
|
617 | const res = await contractObject.methods.init.get('123', 1, 'hahahaha')
|
618 | res.result.should.have.property('gasUsed')
|
619 | res.result.should.have.property('returnType')
|
620 | })
|
621 | it('Dry-run deploy fn on specific account', async () => {
|
622 | const current = await contract.address()
|
623 | const onAccount = contract.addresses().find(acc => acc !== current)
|
624 | const { result } = await contractObject.methods.init.get('123', 1, 'hahahaha', { onAccount })
|
625 | result.should.have.property('gasUsed')
|
626 | result.should.have.property('returnType')
|
627 | result.callerId.should.be.equal(onAccount)
|
628 | })
|
629 | it('Can deploy/call using AEVM', async () => {
|
630 | await contractObject.compile({ backend: 'aevm' })
|
631 | const deployStatic = await contractObject.methods.init.get('123', 1, 'hahahaha', { backend: 'aevm' })
|
632 | deployStatic.should.be.an('object')
|
633 | deployed = await contractObject.methods.init.send('123', 1, 'hahahaha', { backend: 'aevm' })
|
634 | deployed.should.be.an('object')
|
635 | const { result } = await contractObject.methods.intFn(123, { backend: 'aevm' })
|
636 | result.should.have.property('gasUsed')
|
637 | result.should.have.property('returnType')
|
638 | await contractObject.compile()
|
639 | })
|
640 | it('Deploy contract before compile', async () => {
|
641 | contractObject.compiled = null
|
642 | await contractObject.methods.init('123', 1, 'hahahaha')
|
643 | const isCompiled = contractObject.compiled.length && contractObject.compiled.slice(0, 3) === 'cb_'
|
644 | isCompiled.should.be.equal(true)
|
645 | })
|
646 | it('Deploy/Call contract with { waitMined: false }', async () => {
|
647 | const deployed = await contractObject.methods.init('123', 1, 'hahahaha', { waitMined: false })
|
648 | await contract.poll(deployed.transaction)
|
649 | Boolean(deployed.result === undefined).should.be.equal(true)
|
650 | Boolean(deployed.txData === undefined).should.be.equal(true)
|
651 | const result = await contractObject.methods.intFn.send(2, { waitMined: false })
|
652 | Boolean(result.result === undefined).should.be.equal(true)
|
653 | Boolean(result.txData === undefined).should.be.equal(true)
|
654 | await contract.poll(result.hash)
|
655 | })
|
656 | it('Generate ACI object with corresponding bytecode', async () => {
|
657 | await contract.getContractInstance(testContract, { contractAddress: contractObject.deployInfo.address, filesystem, opt: { ttl: 0 } })
|
658 | })
|
659 | it('Generate ACI object with not corresponding bytecode', async () => {
|
660 | try {
|
661 | await contract.getContractInstance(identityContract, { contractAddress: contractObject.deployInfo.address, opt: { ttl: 0 } })
|
662 | } catch (e) {
|
663 | e.message.should.be.equal('Contract source do not correspond to the contract bytecode deployed on the chain')
|
664 | }
|
665 | })
|
666 | it('Generate ACI object with not corresponding bytecode and force this check', async () => {
|
667 | await contract.getContractInstance(identityContract, { forceCodeCheck: true, contractAddress: contractObject.deployInfo.address, opt: { ttl: 0 } })
|
668 | })
|
669 | it('Throw error on creating contract instance with invalid contractAddress', async () => {
|
670 | try {
|
671 | await contract.getContractInstance(testContract, { filesystem, contractAddress: 'ct_asdasdasd', opt: { ttl: 0 } })
|
672 | } catch (e) {
|
673 | e.message.should.be.equal('Invalid contract address')
|
674 | }
|
675 | })
|
676 | it('Throw error on creating contract instance with contract address which is not found on-chain or not active', async () => {
|
677 | const contractAddress = 'ct_ptREMvyDbSh1d38t4WgYgac5oLsa2v9xwYFnG7eUWR8Er5cmT'
|
678 | try {
|
679 | await contract.getContractInstance(testContract, { filesystem, contractAddress, opt: { ttl: 0 } })
|
680 | } catch (e) {
|
681 | e.message.should.be.equal(`Contract with address ${contractAddress} not found on-chain or not active`)
|
682 | }
|
683 | })
|
684 | it('Fail on paying to not payable function', async () => {
|
685 | const amount = 100
|
686 | try {
|
687 | await contractObject.methods.intFn.send(1, { amount })
|
688 | } catch (e) {
|
689 | e.message.should.be.equal(`You try to pay "${amount}" to function "intFn" which is not payable. Only payable function can accept tokens`)
|
690 | }
|
691 | })
|
692 | it('Can pay to payable function', async () => {
|
693 | const contractBalance = await contract.balance(contractObject.deployInfo.address)
|
694 | await contractObject.methods.stringFn.send('1', { amount: 100 })
|
695 | const balanceAfter = await contract.balance(contractObject.deployInfo.address)
|
696 | balanceAfter.should.be.equal(`${+contractBalance + 100}`)
|
697 | })
|
698 | it('Call contract on specific account', async () => {
|
699 | const current = await contract.address()
|
700 | const onAccount = contract.addresses().find(acc => acc !== current)
|
701 | const { result } = await contractObject.methods.intFn('123', { onAccount })
|
702 | result.callerId.should.be.equal(onAccount)
|
703 | })
|
704 | describe('Arguments Validation and Casting', function () {
|
705 | describe('INT', function () {
|
706 | it('Invalid', async () => {
|
707 | try {
|
708 | await contractObject.methods.intFn('asd')
|
709 | } catch (e) {
|
710 | e.message.should.be.equal('"Argument" at position 0 fails because [Value "[asd]" at path: [0] not a number]')
|
711 | }
|
712 | })
|
713 | it('Valid', async () => {
|
714 | const { decodedResult } = await contractObject.methods.intFn.get(1)
|
715 | decodedResult.toString().should.be.equal('1')
|
716 | })
|
717 | })
|
718 | describe('BOOL', function () {
|
719 | it('Invalid', async () => {
|
720 | try {
|
721 | await contractObject.methods.boolFn({})
|
722 | } catch (e) {
|
723 | e.message.should.be.equal('"Argument" at position 0 fails because [Value "[[object Object]]" at path: [0] not a boolean]')
|
724 | }
|
725 | })
|
726 | it('Valid', async () => {
|
727 | const { decodedResult } = await contractObject.methods.boolFn.get(true)
|
728 | decodedResult.should.be.equal(true)
|
729 | })
|
730 | })
|
731 | describe('STRING', function () {
|
732 | it('Invalid', async () => {
|
733 | try {
|
734 | await contractObject.methods.stringFn(123)
|
735 | } catch (e) {
|
736 | e.message.should.be.equal('"Argument" at position 0 fails because [Value "123" at path: [0] not a string]')
|
737 | }
|
738 | })
|
739 | it('Valid', async () => {
|
740 | const { decodedResult } = await contractObject.methods.stringFn('string')
|
741 | decodedResult.should.be.equal('string')
|
742 | })
|
743 | })
|
744 | describe('ADDRESS', function () {
|
745 | it('Invalid address', async () => {
|
746 | try {
|
747 | await contractObject.methods.addressFn('asdasasd')
|
748 | } catch (e) {
|
749 | e.message.should.be.equal('"Argument" at position 0 fails because ["[asdasasd]" with value "asdasasd" fails to match the required pattern: /^(ak_|ct_|ok_|oq_)/]')
|
750 | }
|
751 | })
|
752 | it('Invalid address type', async () => {
|
753 | try {
|
754 | await contractObject.methods.addressFn(333)
|
755 | } catch (e) {
|
756 | e.message.should.be.equal('"Argument" at position 0 fails because [Value "333" at path: [0] not a string]')
|
757 | }
|
758 | })
|
759 | it('Return address', async () => {
|
760 | const { decodedResult } = await contractObject.methods.accountAddress(await contract.address())
|
761 | decodedResult.should.be.equal(await contract.address())
|
762 | })
|
763 | it('Valid', async () => {
|
764 | const { decodedResult } = await contractObject.methods.addressFn('ak_2ct6nMwmRnyGX6jPhraFPedZ5bYp1GXqpvnAq5LXeL5TTPfFif')
|
765 | decodedResult.should.be.equal('ak_2ct6nMwmRnyGX6jPhraFPedZ5bYp1GXqpvnAq5LXeL5TTPfFif')
|
766 | })
|
767 | })
|
768 | describe('TUPLE', function () {
|
769 | it('Invalid type', async () => {
|
770 | try {
|
771 | await contractObject.methods.tupleFn('asdasasd')
|
772 | } catch (e) {
|
773 | e.message.should.be.equal('"Argument" at position 0 fails because [Value "[asdasasd]" at path: [0] not a array]')
|
774 | }
|
775 | })
|
776 | it('Invalid tuple prop type', async () => {
|
777 | try {
|
778 | await contractObject.methods.tupleFn([1, 'string'])
|
779 | } catch (e) {
|
780 | e.message.should.be.equal('"Argument" at position 0 fails because ["[1,string]" at position 0 fails because [Value "1" at path: [0,0] not a string], "[1,string]" at position 1 fails because [Value "1" at path: [0,1] not a number]]')
|
781 | }
|
782 | })
|
783 | it('Required tuple prop', async () => {
|
784 | try {
|
785 | await contractObject.methods.tupleFn([1])
|
786 | } catch (e) {
|
787 | e.message.should.be.equal('"Argument" at position 0 fails because ["[1]" at position 0 fails because [Value "1" at path: [0,0] not a string], "[1]" does not contain 1 required value(s)]')
|
788 | }
|
789 | })
|
790 | it('Wrong type in list inside tuple', async () => {
|
791 | try {
|
792 | await contractObject.methods.tupleWithList([[true], 1])
|
793 | } catch (e) {
|
794 | e.message.should.be.equal('"Argument" at position 0 fails because ["[true,1]" at position 0 fails because ["0" at position 0 fails because [Value "0" at path: [0,0,0] not a number]]]')
|
795 | }
|
796 | })
|
797 | it('Wrong type in tuple inside tuple', async () => {
|
798 | try {
|
799 | await contractObject.methods.tupleInTupleFn([['str', 1], 1])
|
800 | } catch (e) {
|
801 | e.message.should.be.equal('"Argument" at position 0 fails because ["[str,1,1]" at position 0 fails because ["Tuple argument" at position 1 fails because [Value "1" at path: [0,0,1] not a string]]]')
|
802 | }
|
803 | })
|
804 | it('Valid', async () => {
|
805 | const { decodedResult } = await contractObject.methods.tupleFn(['test', 1])
|
806 | JSON.stringify(decodedResult).should.be.equal(JSON.stringify(['test', 1]))
|
807 | })
|
808 | })
|
809 | describe('LIST', function () {
|
810 | it('Invalid type', async () => {
|
811 | try {
|
812 | await contractObject.methods.listFn('asdasasd')
|
813 | } catch (e) {
|
814 | e.message.should.be.equal('"Argument" at position 0 fails because [Value "[asdasasd]" at path: [0] not a array]')
|
815 | }
|
816 | })
|
817 | it('Invalid list element type', async () => {
|
818 | try {
|
819 | await contractObject.methods.listFn([1, 'string'])
|
820 | } catch (e) {
|
821 | e.message.should.be.equal('"Argument" at position 0 fails because ["[1,string]" at position 1 fails because [Value "1" at path: [0,1] not a number]]')
|
822 | }
|
823 | })
|
824 | it('Invalid list element type nested', async () => {
|
825 | try {
|
826 | await contractObject.methods.listInListFn([['childListWronmgElement'], 'parentListWrongElement'])
|
827 | } catch (e) {
|
828 | e.message.should.be.equal('"Argument" at position 0 fails because ["[childListWronmgElement,parentListWrongElement]" at position 0 fails because ["0" at position 0 fails because [Value "0" at path: [0,0,0] not a number]], "[childListWronmgElement,parentListWrongElement]" at position 1 fails because [Value "1" at path: [0,1] not a array]]')
|
829 | }
|
830 | })
|
831 | it('Valid', async () => {
|
832 | const { decodedResult } = await contractObject.methods.listInListFn([[1, 2], [3, 4]])
|
833 | JSON.stringify(decodedResult).should.be.equal(JSON.stringify([[1, 2], [3, 4]]))
|
834 | })
|
835 | })
|
836 | describe('MAP', function () {
|
837 | it('Valid', async () => {
|
838 | const address = await contract.address()
|
839 | const mapArg = new Map(
|
840 | [
|
841 | [address, ['someStringV', 324]]
|
842 | ]
|
843 | )
|
844 | const objectFromMap = Array.from(mapArg.entries()).reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
|
845 | const { decodedResult } = await contractObject.methods.mapFn(objectFromMap)
|
846 | JSON.stringify(decodedResult).should.be.equal(JSON.stringify(Array.from(mapArg.entries())))
|
847 | })
|
848 | it('Map With Option Value', async () => {
|
849 | const address = await contract.address()
|
850 | const mapArgWithSomeValue = new Map(
|
851 | [
|
852 | [address, ['someStringV', 123]]
|
853 | ]
|
854 | )
|
855 | const mapArgWithNoneValue = new Map(
|
856 | [
|
857 | [address, ['someStringV', undefined]]
|
858 | ]
|
859 | )
|
860 | const returnArgWithSomeValue = new Map(
|
861 | [
|
862 | [address, ['someStringV', 123]]
|
863 | ]
|
864 | )
|
865 | const returnArgWithNoneValue = new Map(
|
866 | [
|
867 | [address, ['someStringV', undefined]]
|
868 | ]
|
869 | )
|
870 | const resultWithSome = await contractObject.methods.mapOptionFn(mapArgWithSomeValue)
|
871 | const resultWithNone = await contractObject.methods.mapOptionFn(mapArgWithNoneValue)
|
872 |
|
873 | const decodedSome = resultWithSome.decodedResult
|
874 |
|
875 | JSON.stringify(decodedSome).should.be.equal(JSON.stringify(Array.from(returnArgWithSomeValue.entries())))
|
876 | JSON.stringify(resultWithNone.decodedResult).should.be.equal(JSON.stringify(Array.from(returnArgWithNoneValue.entries())))
|
877 | })
|
878 | it('Cast from string to int', async () => {
|
879 | const address = await contract.address()
|
880 | const mapArg = new Map(
|
881 | [
|
882 | [address, ['someStringV', '324']]
|
883 | ]
|
884 | )
|
885 | const result = await contractObject.methods.mapFn(mapArg)
|
886 | mapArg.set(address, ['someStringV', 324])
|
887 | JSON.stringify(result.decodedResult).should.be.equal(JSON.stringify(Array.from(mapArg.entries())))
|
888 | })
|
889 | it('Cast from array to map', async () => {
|
890 | const address = await contract.address()
|
891 | const mapArg =
|
892 | [
|
893 | [address, ['someStringV', 324]]
|
894 | ]
|
895 | const { decodedResult } = await contractObject.methods.mapFn(mapArg)
|
896 | JSON.stringify(decodedResult).should.be.equal(JSON.stringify(mapArg))
|
897 | })
|
898 | })
|
899 | describe('RECORD/STATE', function () {
|
900 | const objEq = (obj, obj2) => !Object.entries(obj).find(([key, val]) => JSON.stringify(obj2[key]) !== JSON.stringify(val))
|
901 | it('Valid Set Record (Cast from JS object)', async () => {
|
902 | await contractObject.methods.setRecord({ value: 'qwe', key: 1234, testOption: 'test' })
|
903 | const state = await contractObject.methods.getRecord()
|
904 |
|
905 | objEq(state.decodedResult, { value: 'qwe', key: 1234, testOption: 'test' }).should.be.equal(true)
|
906 | })
|
907 | it('Get Record(Convert to JS object)', async () => {
|
908 | const result = await contractObject.methods.getRecord()
|
909 | objEq(result.decodedResult, { value: 'qwe', key: 1234, testOption: 'test' }).should.be.equal(true)
|
910 | })
|
911 | it('Get Record With Option (Convert to JS object)', async () => {
|
912 | await contractObject.methods.setRecord({ key: 1234, value: 'qwe', testOption: 'resolved string' })
|
913 | const result = await contractObject.methods.getRecord()
|
914 | objEq(result.decodedResult, { value: 'qwe', key: 1234, testOption: 'resolved string' }).should.be.equal(true)
|
915 | })
|
916 | it('Invalid value type', async () => {
|
917 | try {
|
918 | await contractObject.methods.setRecord({ value: 123, key: 'test' })
|
919 | } catch (e) {
|
920 | e.message.should.be.equal('"Argument" at position 0 fails because [child "value" fails because [Value "123" at path: [0,value] not a string], child "key" fails because [Value "key" at path: [0,key] not a number]]')
|
921 | }
|
922 | })
|
923 | })
|
924 | describe('OPTION', function () {
|
925 | it('Set Some Option Value(Cast from JS value/Convert result to JS)', async () => {
|
926 | const optionRes = await contractObject.methods.intOption(123)
|
927 |
|
928 | optionRes.decodedResult.should.be.equal(123)
|
929 | })
|
930 | it('Set Some Option List Value(Cast from JS value/Convert result to JS)', async () => {
|
931 | const optionRes = await contractObject.methods.listOption([[1, 'testString']])
|
932 |
|
933 | JSON.stringify(optionRes.decodedResult).should.be.equal(JSON.stringify([[1, 'testString']]))
|
934 | })
|
935 | it('Set None Option Value(Cast from JS value/Convert to JS)', async () => {
|
936 | const optionRes = await contractObject.methods.intOption(undefined)
|
937 | const isUndefined = optionRes.decodedResult === undefined
|
938 | isUndefined.should.be.equal(true)
|
939 | })
|
940 | it('Invalid option type', async () => {
|
941 | try {
|
942 | await contractObject.methods.intOption('string')
|
943 | } catch (e) {
|
944 | e.message.should.be.equal('"Argument" at position 0 fails because [Value "[string]" at path: [0] not a number]')
|
945 | }
|
946 | })
|
947 | })
|
948 | describe('NAMESPACES', function () {
|
949 | it('Use namespace in function body', async () => {
|
950 | const res = await contractObject.methods.usingExternalLib(2)
|
951 |
|
952 | res.decodedResult.should.be.equal(4)
|
953 | })
|
954 | })
|
955 | describe('DATATYPE', function () {
|
956 | it('Invalid type', async () => {
|
957 | try {
|
958 | await contractObject.methods.datTypeFn({})
|
959 | } catch (e) {
|
960 | e.message.should.be.equal('"Argument" at position 0 fails because ["0" must be a string, "value" must contain at least one of [Year, Month, Day]]')
|
961 | }
|
962 | })
|
963 | it('Call generic datatype', async () => {
|
964 | const res = await contractObject.methods.datTypeGFn({ Left: [2] })
|
965 | res.decodedResult.should.be.equal(2)
|
966 | })
|
967 | it('Invalid arguments length', async () => {
|
968 | try {
|
969 | await contractObject.methods.datTypeGFn()
|
970 | } catch (e) {
|
971 | e.message.should.be.equal('Function "datTypeGFn" require 1 arguments of types [{"StateContract.one_or_both":["int","string"]}] but get []')
|
972 | }
|
973 | })
|
974 | it('Invalid variant', async () => {
|
975 | try {
|
976 | await contractObject.methods.datTypeFn('asdcxz')
|
977 | } catch (e) {
|
978 | e.message.should.be.equal('"Argument" at position 0 fails because ["0" must be one of [Year, Month, Day], "0" must be an object]')
|
979 | }
|
980 | })
|
981 | it('Valid', async () => {
|
982 | const res = await contractObject.methods.datTypeFn('Year')
|
983 | JSON.stringify(res.decodedResult).should.be.equal('"Year"')
|
984 | })
|
985 | })
|
986 | describe('Hash', function () {
|
987 | it('Invalid type', async () => {
|
988 | try {
|
989 | await contractObject.methods.hashFn({})
|
990 | } catch (e) {
|
991 | e.message.should.be.equal('The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of Object')
|
992 | }
|
993 | })
|
994 | it('Invalid length', async () => {
|
995 | const address = await contract.address()
|
996 | const decoded = Buffer.from(decode(address, 'ak').slice(1))
|
997 | try {
|
998 | await contractObject.methods.hashFn(decoded)
|
999 | } catch (e) {
|
1000 | const isSizeCheck = e.message.indexOf('not a 32 bytes') !== -1
|
1001 | isSizeCheck.should.be.equal(true)
|
1002 | }
|
1003 | })
|
1004 | it('Valid', async () => {
|
1005 | const address = await contract.address()
|
1006 | const decoded = decode(address, 'ak')
|
1007 | const hashAsBuffer = await contractObject.methods.hashFn(decoded)
|
1008 | const hashAsHex = await contractObject.methods.hashFn(decoded.toString('hex'))
|
1009 | hashAsBuffer.decodedResult.should.be.equal(decoded.toString('hex'))
|
1010 | hashAsHex.decodedResult.should.be.equal(decoded.toString('hex'))
|
1011 | })
|
1012 | })
|
1013 | describe('Signature', function () {
|
1014 | it('Invalid type', async () => {
|
1015 | try {
|
1016 | await contractObject.methods.signatureFn({})
|
1017 | } catch (e) {
|
1018 | e.message.should.be.equal('The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of Object')
|
1019 | }
|
1020 | })
|
1021 | it('Invalid length', async () => {
|
1022 | const address = await contract.address()
|
1023 | const decoded = decode(address, 'ak')
|
1024 | try {
|
1025 | await contractObject.methods.signatureFn(decoded)
|
1026 | } catch (e) {
|
1027 | const isSizeCheck = e.message.indexOf('not a 64 bytes') !== -1
|
1028 | isSizeCheck.should.be.equal(true)
|
1029 | }
|
1030 | })
|
1031 | it('Valid', async () => {
|
1032 | const address = await contract.address()
|
1033 | const decoded = decode(address, 'ak')
|
1034 | const fakeSignature = Buffer.from(await contract.sign(decoded))
|
1035 | const hashAsBuffer = await contractObject.methods.signatureFn(fakeSignature)
|
1036 | const hashAsHex = await contractObject.methods.signatureFn(fakeSignature.toString('hex'))
|
1037 | hashAsBuffer.decodedResult.should.be.equal(fakeSignature.toString('hex'))
|
1038 | hashAsHex.decodedResult.should.be.equal(fakeSignature.toString('hex'))
|
1039 | })
|
1040 | })
|
1041 | describe('Bytes', function () {
|
1042 | it('Invalid type', async () => {
|
1043 | try {
|
1044 | await contractObject.methods.bytesFn({})
|
1045 | } catch (e) {
|
1046 | e.message.should.be.equal('The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of Object')
|
1047 | }
|
1048 | })
|
1049 | it('Invalid length', async () => {
|
1050 | const address = await contract.address()
|
1051 | const decoded = decode(address, 'ak')
|
1052 | try {
|
1053 | await contractObject.methods.bytesFn(Buffer.from([...decoded, 2]))
|
1054 | } catch (e) {
|
1055 | const isSizeCheck = e.message.indexOf('not a 32 bytes') !== -1
|
1056 | isSizeCheck.should.be.equal(true)
|
1057 | }
|
1058 | })
|
1059 | it('Valid', async () => {
|
1060 | const address = await contract.address()
|
1061 | const decoded = decode(address, 'ak')
|
1062 | const hashAsBuffer = await contractObject.methods.bytesFn(decoded)
|
1063 | const hashAsHex = await contractObject.methods.bytesFn(decoded.toString('hex'))
|
1064 | hashAsBuffer.decodedResult.should.be.equal(decoded.toString('hex'))
|
1065 | hashAsHex.decodedResult.should.be.equal(decoded.toString('hex'))
|
1066 | })
|
1067 | })
|
1068 | })
|
1069 | describe('Call contract', function () {
|
1070 | it('Call contract using using sophia type arguments', async () => {
|
1071 | contractObject.setOptions({ skipArgsConvert: true })
|
1072 | const res = await contractObject.methods.listFn('[ 1, 2 ]')
|
1073 | contractObject.setOptions({ skipArgsConvert: false })
|
1074 | return res.decode().should.eventually.become([1, 2])
|
1075 | })
|
1076 | it('Call contract using using js type arguments', async () => {
|
1077 | const res = await contractObject.methods.listFn([1, 2])
|
1078 | return res.decode().should.eventually.become([1, 2])
|
1079 | })
|
1080 | it('Call contract using using js type arguments and skip result transform', async () => {
|
1081 | contractObject.setOptions({ skipTransformDecoded: true })
|
1082 | const res = await contractObject.methods.listFn([1, 2])
|
1083 | const decoded = await res.decode()
|
1084 | const decodedJSON = JSON.stringify([1, 2])
|
1085 | contractObject.setOptions({ skipTransformDecoded: false })
|
1086 | JSON.stringify(decoded).should.be.equal(decodedJSON)
|
1087 | })
|
1088 | it('Call contract with contract type argument', async () => {
|
1089 | const result = await contractObject.methods.approve(0, 'ct_AUUhhVZ9de4SbeRk8ekos4vZJwMJohwW5X8KQjBMUVduUmoUh')
|
1090 | return result.decode().should.eventually.become(0)
|
1091 | })
|
1092 | })
|
1093 | describe('Type resolving', () => {
|
1094 | let cInstance
|
1095 | before(async () => {
|
1096 | cInstance = await contract.getContractInstance(testContract, { filesystem })
|
1097 | })
|
1098 | it('Resolve remote contract type', async () => {
|
1099 | const fnACI = getFunctionACI(cInstance.aci, 'remoteContract', { external: cInstance.externalAci })
|
1100 | readType('Voting', { bindings: fnACI.bindings }).t.should.be.equal('address')
|
1101 | })
|
1102 | it('Resolve external contract type', async () => {
|
1103 | const fnACI = getFunctionACI(cInstance.aci, 'remoteArgs', { external: cInstance.externalAci })
|
1104 | readType(fnACI.arguments[0].type, { bindings: fnACI.bindings }).should.eql(JSON.parse('{"t":"record","generic":[{"name":"value","type":"string"},{"name":"key","type":{"list":["Voting.test_type"]}}]}'))
|
1105 | readType(fnACI.returns, { bindings: fnACI.bindings }).t.should.be.equal('int')
|
1106 | })
|
1107 | })
|
1108 | })
|
1109 | })
|