8
8
# those terms.
9
9
10
10
import
11
- std/ [options, json, tables, uri],
11
+ std/ [options, json, tables, uri, macros ],
12
12
stint, httputils, chronos,
13
13
json_rpc/ [rpcclient, jsonmarshal],
14
14
json_rpc/ private/ jrpc_sys,
42
42
web3* : Web3
43
43
contractAddress* : Address
44
44
45
+ Web3AsyncSenderImpl = ref object
46
+ web3* : Web3
47
+ contractAddress* : Address
48
+ defaultAccount* : Address
49
+ value* : UInt256
50
+ gas* : uint64
51
+ gasPrice* : int
52
+ chainId* : Option [ChainId ]
53
+ blockNumber* : uint64
54
+
45
55
Sender * [T] = ContractInstance [T, Web3SenderImpl ]
56
+ AsyncSender * [T] = ContractInstance [T, Web3AsyncSenderImpl ]
46
57
47
58
SubscriptionEventHandler * = proc (j: JsonString ) {.gcsafe , raises : [].}
48
59
SubscriptionErrorHandler * = proc (err: CatchableError ) {.gcsafe , raises : [].}
58
69
historicalEventsProcessed: bool
59
70
removed: bool
60
71
72
+ ContractInvocation * [TResult , TSender ] = object
73
+ data* : seq [byte ]
74
+ sender* : TSender
75
+
61
76
proc getValue (params: RequestParamsRx , field: string , FieldType: type ):
62
77
Result [FieldType , string ] {.gcsafe , raises : [].} =
63
78
try :
@@ -297,23 +312,24 @@ proc send*(web3: Web3, c: EthSend, chainId: ChainId): Future[TxHash] {.async.} =
297
312
let t = encodeTransaction (cc, web3.privateKey.get (), chainId)
298
313
return await web3.provider.eth_sendRawTransaction (t)
299
314
300
- proc sendData (sender: Web3SenderImpl ,
315
+ proc sendData (web3: Web3 ,
316
+ contractAddress: Address ,
317
+ defaultAccount: Address ,
301
318
data: seq [byte ],
302
319
value: UInt256 ,
303
320
gas: uint64 ,
304
321
gasPrice: int ,
305
322
chainId = none (ChainId )): Future [TxHash ] {.async .} =
306
323
let
307
- web3 = sender.web3
308
324
gasPrice = if web3.privateKey.isSome () or gasPrice != 0 : some (gasPrice.Quantity )
309
325
else : none (Quantity )
310
326
nonce = if web3.privateKey.isSome (): some (await web3.nextNonce ())
311
327
else : none (Quantity )
312
328
313
329
cc = EthSend (
314
330
data: data,
315
- `from`: web3. defaultAccount,
316
- to: some (sender. contractAddress),
331
+ `from`: defaultAccount,
332
+ to: some (contractAddress),
317
333
gas: some (Quantity (gas)),
318
334
value: some (value),
319
335
nonce: nonce,
@@ -329,36 +345,41 @@ proc send*[T](c: ContractInvocation[T, Web3SenderImpl],
329
345
value = 0 .u256,
330
346
gas = 3000000 'u64 ,
331
347
gasPrice = 0 ): Future [TxHash ] =
332
- sendData (c.sender, c.data, value, gas, gasPrice)
348
+ sendData (c.sender.web3, c.sender.contractAddress, c.sender.web3.defaultAccount , c.data, value, gas, gasPrice)
333
349
334
350
proc send * [T](c: ContractInvocation [T, Web3SenderImpl ],
335
351
chainId: ChainId ,
336
352
value = 0 .u256,
337
353
gas = 3000000 'u64 ,
338
354
gasPrice = 0 ): Future [TxHash ] =
339
- sendData (c.sender, c.data, value, gas, gasPrice, some (chainId))
355
+ sendData (c.sender.web3, c.sender.contractAddress, c.sender.web3.defaultAccount , c.data, value, gas, gasPrice, some (chainId))
340
356
341
- proc call * [T](c: ContractInvocation [T, Web3SenderImpl ],
357
+ proc callAux (web3: Web3 ,
358
+ contractAddress: Address ,
359
+ defaultAccount: Address ,
360
+ data: seq [byte ],
342
361
value = 0 .u256,
343
362
gas = 3000000 'u64 ,
344
- blockNumber = high (uint64 )): Future [T] {.async .} =
345
- let web3 = c.sender.web3
363
+ blockNumber = high (uint64 )): Future [seq [byte ]] {.async .} =
346
364
var cc: EthCall
347
- cc.data = some (c. data)
348
- cc.source = some (web3. defaultAccount)
349
- cc.to = some (c.sender. contractAddress)
365
+ cc.data = some (data)
366
+ cc.source = some (defaultAccount)
367
+ cc.to = some (contractAddress)
350
368
cc.gas = some (Quantity (gas))
351
369
cc.value = some (value)
352
- let response =
370
+ result =
353
371
if blockNumber != high (uint64 ):
354
372
await web3.provider.eth_call (cc, blockId (blockNumber))
355
373
else :
356
374
await web3.provider.eth_call (cc, " latest" )
357
375
376
+ proc call * [T](c: ContractInvocation [T, Web3SenderImpl ],
377
+ value = 0 .u256,
378
+ gas = 3000000 'u64 ,
379
+ blockNumber = high (uint64 )): Future [T] {.async .} =
380
+ let response = await callAux (c.sender.web3, c.sender.contractAddress, c.sender.web3.defaultAccount, c.data, value, gas, blockNumber)
358
381
if response.len > 0 :
359
- var res: T
360
- discard decode (response, 0 , 0 , res)
361
- return res
382
+ discard decode (response, 0 , 0 , result )
362
383
else :
363
384
raise newException (CatchableError , " No response from the Web3 provider" )
364
385
@@ -409,6 +430,43 @@ proc exec*[T](c: ContractInvocation[T, Web3SenderImpl], value = 0.u256, gas = 30
409
430
proc contractSender * (web3: Web3 , T: typedesc , toAddress: Address ): Sender [T] =
410
431
Sender [T](sender: Web3SenderImpl (web3: web3, contractAddress: toAddress))
411
432
433
+ proc createMutableContractInvocation * (sender: Web3SenderImpl , ReturnType: typedesc , data: sink seq [byte ]): ContractInvocation [ReturnType , Web3SenderImpl ] {.inline .} =
434
+ ContractInvocation [ReturnType , Web3SenderImpl ](sender: sender, data: data)
435
+
436
+ proc createImmutableContractInvocation * (sender: Web3SenderImpl , ReturnType: typedesc , data: sink seq [byte ]): ContractInvocation [ReturnType , Web3SenderImpl ] {.inline .} =
437
+ ContractInvocation [ReturnType , Web3SenderImpl ](sender: sender, data: data)
438
+
439
+ proc contractInstance * (web3: Web3 , T: typedesc , toAddress: Address ): AsyncSender [T] =
440
+ AsyncSender [T](sender: Web3AsyncSenderImpl (web3: web3, contractAddress: toAddress, defaultAccount: web3.defaultAccount, gas: 3000000 , blockNumber: uint64 .high))
441
+
442
+ proc createMutableContractInvocation * (sender: Web3AsyncSenderImpl , ReturnType: typedesc , data: sink seq [byte ]) {.async .} =
443
+ assert (sender.gas > 0 )
444
+ let h = await sendData (sender.web3, sender.contractAddress, sender.defaultAccount, data, sender.value, sender.gas, sender.gasPrice, sender.chainId)
445
+ let receipt = await sender.web3.getMinedTransactionReceipt (h)
446
+
447
+ proc createImmutableContractInvocation * (sender: Web3AsyncSenderImpl , ReturnType: typedesc , data: sink seq [byte ]): Future [ReturnType ] {.async .} =
448
+ let response = await callAux (sender.web3, sender.contractAddress, sender.defaultAccount, data, sender.value, sender.gas, sender.blockNumber)
449
+ if response.len > 0 :
450
+ discard decode (response, 0 , 0 , result )
451
+ else :
452
+ raise newException (CatchableError , " No response from the Web3 provider" )
453
+
454
+ proc deployContractAux (web3: Web3 , data: seq [byte ], gasPrice = 0 ): Future [Address ] {.async .} =
455
+ var tr: EthSend
456
+ tr.`from` = web3.defaultAccount
457
+ tr.data = data
458
+ tr.gas = Quantity (30000000 ).some
459
+ if gasPrice != 0 :
460
+ tr.gasPrice = some (gasPrice.Quantity )
461
+
462
+ let h = await web3.send (tr)
463
+ let r = await web3.getMinedTransactionReceipt (h)
464
+ return r.contractAddress.get
465
+
466
+ proc createContractDeployment * (web3: Web3 , ContractType: typedesc , data: sink seq [byte ]): Future [AsyncSender [ContractType ]] {.async .} =
467
+ let a = await deployContractAux (web3, data, gasPrice = 0 )
468
+ return contractInstance (web3, ContractType , a)
469
+
412
470
proc isDeployed * (s: Sender , atBlock: RtBlockIdentifier ): Future [bool ] {.async .} =
413
471
let
414
472
codeFut = case atBlock.kind
@@ -425,3 +483,24 @@ proc isDeployed*(s: Sender, atBlock: RtBlockIdentifier): Future[bool] {.async.}
425
483
426
484
proc subscribe * [TContract](s: Sender [TContract ], t: typedesc , cb: proc ): Future [Subscription ] {.inline .} =
427
485
subscribe (s, t, FilterOptions (), cb, SubscriptionErrorHandler nil )
486
+
487
+ proc copy [T](s: AsyncSender [T]): AsyncSender [T] =
488
+ result = s
489
+ result .sender.new ()
490
+ result .sender[] = s.sender[]
491
+
492
+ macro adjust * (s: AsyncSender , modifications: varargs [untyped ]): untyped =
493
+ # # Copies AsyncSender, modifying its properties. E.g.
494
+ # # myContract.adjust(gas = 1000, value = 5.u256).myContractMethod()
495
+ let cp = genSym (nskLet, " cp" )
496
+ result = quote do :
497
+ block :
498
+ let `cp` = copy (`s`)
499
+
500
+ for s in modifications:
501
+ s.expectKind (nnkExprEqExpr)
502
+ let fieldName = s[0 ]
503
+ let fieldVal = s[1 ]
504
+ result [1 ].add quote do :
505
+ `cp`.sender.`fieldName` = `fieldVal`
506
+ result [1 ].add (cp)
0 commit comments