Skip to content

Commit eea0344

Browse files
committed
More flexibility to contract DSL, Async contract caller
1 parent be20b57 commit eea0344

File tree

4 files changed

+133
-52
lines changed

4 files changed

+133
-52
lines changed

tests/test_contract_dsl.nim

+8-4
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@ import
1616
type
1717
DummySender = object
1818

19+
proc createMutableContractInvocation(s: DummySender, t: typedesc, data: seq[byte]): seq[byte] = data
20+
proc createImmutableContractInvocation(s: DummySender, t: typedesc, data: seq[byte]): seq[byte] = data
21+
proc createContractDeployment(s: DummySender, t: typedesc, data: seq[byte]): seq[byte] = data
22+
1923
proc instantiateContract(t: typedesc): ContractInstance[t, DummySender] =
2024
discard
2125

22-
proc checkData(d: ContractInvocation | ContractDeployment, expectedData: string) =
26+
proc checkData(a: seq[byte], expectedData: string) =
2327
let b = hexToSeqByte(expectedData)
24-
if d.data != b:
25-
echo "actual: ", d.data.to0xHex()
28+
if a != b:
29+
echo "actual: ", a.to0xHex()
2630
echo "expect: ", b.to0xHex()
27-
doAssert(d.data == b)
31+
doAssert(a == b)
2832

2933
contract(TestContract):
3034
proc getBool(): bool

tests/test_contracts.nim

+13-13
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ contract(EncodingTest):
2323
proc setBool(val: bool)
2424
proc getBool(): bool {.view.}
2525
proc setString(a: string)
26-
proc getString(): string
26+
proc getString(): string {.view.}
2727
proc setData1(a: UInt256, d: seq[byte])
28-
proc getData1(): Data1
29-
proc getManyData1(): seq[Data1]
28+
proc getData1(): Data1 {.view.}
29+
proc getManyData1(): seq[Data1] {.view.}
3030

3131
const EncodingTestCode = "0x6000805460ff1916815560a0604052608090815260019061002090826100d2565b5034801561002d57600080fd5b50610191565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061005d57607f821691505b60208210810361007d57634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156100cd57600081815260208120601f850160051c810160208610156100aa5750805b601f850160051c820191505b818110156100c9578281556001016100b6565b5050505b505050565b81516001600160401b038111156100eb576100eb610033565b6100ff816100f98454610049565b84610083565b602080601f831160018114610134576000841561011c5750858301515b600019600386901b1c1916600185901b1785556100c9565b600085815260208120601f198616915b8281101561016357888601518255948401946001909101908401610144565b50858210156101815787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b610a27806101a06000396000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c80637fcaf6661161005b5780637fcaf666146100f157806389ea642f146101045780639944cc71146101195780639fd159e61461012e57600080fd5b806312a7b914146100825780631cb3eebe1461009d5780631e26fd33146100b2575b600080fd5b60005460ff1660405190151581526020015b60405180910390f35b6100b06100ab36600461047e565b610143565b005b6100b06100c03660046104ca565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055565b6100b06100ff3660046104f3565b6101ad565b61010c6101bf565b6040516100949190610599565b610121610251565b60405161009491906105d3565b610136610310565b60405161009491906105e6565b604051806040016040528084815260200183838080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050509152508051600290815560208201516003906101a5908261072e565b505050505050565b60016101ba828483610848565b505050565b6060600180546101ce90610695565b80601f01602080910402602001604051908101604052809291908181526020018280546101fa90610695565b80156102475780601f1061021c57610100808354040283529160200191610247565b820191906000526020600020905b81548152906001019060200180831161022a57829003601f168201915b5050505050905090565b604080518082019091526000815260606020820152604080518082019091526002805482526003805460208401919061028990610695565b80601f01602080910402602001604051908101604052809291908181526020018280546102b590610695565b80156103025780601f106102d757610100808354040283529160200191610302565b820191906000526020600020905b8154815290600101906020018083116102e557829003601f168201915b505050505081525050905090565b60408051600380825260808201909252606091816020015b60408051808201909152600081526060602082015281526020019060019003908161032857905050905060005b815181101561043157604080518082019091526002805482526003805460208401919061038190610695565b80601f01602080910402602001604051908101604052809291908181526020018280546103ad90610695565b80156103fa5780601f106103cf576101008083540402835291602001916103fa565b820191906000526020600020905b8154815290600101906020018083116103dd57829003601f168201915b50505050508152505082828151811061041557610415610963565b60200260200101819052508061042a90610992565b9050610355565b5090565b60008083601f84011261044757600080fd5b50813567ffffffffffffffff81111561045f57600080fd5b60208301915083602082850101111561047757600080fd5b9250929050565b60008060006040848603121561049357600080fd5b83359250602084013567ffffffffffffffff8111156104b157600080fd5b6104bd86828701610435565b9497909650939450505050565b6000602082840312156104dc57600080fd5b813580151581146104ec57600080fd5b9392505050565b6000806020838503121561050657600080fd5b823567ffffffffffffffff81111561051d57600080fd5b61052985828601610435565b90969095509350505050565b6000815180845260005b8181101561055b5760208185018101518683018201520161053f565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b6020815260006104ec6020830184610535565b8051825260006020820151604060208501526105cb6040850182610535565b949350505050565b6020815260006104ec60208301846105ac565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b82811015610659577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08886030184526106478583516105ac565b9450928501929085019060010161060d565b5092979650505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600181811c908216806106a957607f821691505b6020821081036106e2577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f8211156101ba57600081815260208120601f850160051c8101602086101561070f5750805b601f850160051c820191505b818110156101a55782815560010161071b565b815167ffffffffffffffff81111561074857610748610666565b61075c816107568454610695565b846106e8565b602080601f8311600181146107af57600084156107795750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556101a5565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156107fc578886015182559484019460019091019084016107dd565b508582101561083857878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b67ffffffffffffffff83111561086057610860610666565b6108748361086e8354610695565b836106e8565b6000601f8411600181146108c657600085156108905750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b17835561095c565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b8281101561091557868501358255602094850194600190920191016108f5565b5086821015610950577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036109ea577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b506001019056fea26469706673582212205dbf820dba2d3dea0502a6521ca26db2e50cf5819a87cc8518ad67dbd8091e3664736f6c63430008130033"
3232

@@ -162,28 +162,28 @@ suite "Contracts":
162162
cc = receipt.contractAddress.get
163163
echo "Deployed EncodingTest contract: ", cc
164164

165-
let ns = web3.contractSender(EncodingTest, cc)
165+
let ns = web3.contractInstance(EncodingTest, cc)
166166

167-
var b = await ns.getBool().call()
167+
var b = await ns.getBool()
168168
assert(b == false)
169-
await ns.setBool(true).exec()
170-
b = await ns.getBool().call()
169+
await ns.setBool(true)
170+
b = await ns.getBool()
171171
assert(b == true)
172172

173-
var s = await ns.getString().call()
173+
var s = await ns.getString()
174174
assert(s == "")
175-
await ns.setString("hello").exec()
176-
s = await ns.getString().call()
175+
await ns.setString("hello")
176+
s = await ns.getString()
177177
assert(s == "hello")
178178

179179
let data1data = @[1.byte, 2, 3, 4, 5]
180-
discard await ns.setData1(123.u256, data1data).send()
180+
await ns.setData1(123.u256, data1data)
181181

182-
let data1 = await ns.getData1().call()
182+
let data1 = await ns.getData1()
183183
assert(data1.a == 123.u256)
184184
assert(data1.data == data1data)
185185

186-
let manyData1 = await ns.getManyData1().call()
186+
let manyData1 = await ns.getManyData1()
187187
assert(manyData1.len == 3)
188188
for i in 0 .. manyData1.high:
189189
assert(manyData1[i].a == 123.u256)

web3.nim

+96-17
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# those terms.
99

1010
import
11-
std/[options, json, tables, uri],
11+
std/[options, json, tables, uri, macros],
1212
stint, httputils, chronos,
1313
json_rpc/[rpcclient, jsonmarshal],
1414
json_rpc/private/jrpc_sys,
@@ -42,7 +42,18 @@ type
4242
web3*: Web3
4343
contractAddress*: Address
4444

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+
4555
Sender*[T] = ContractInstance[T, Web3SenderImpl]
56+
AsyncSender*[T] = ContractInstance[T, Web3AsyncSenderImpl]
4657

4758
SubscriptionEventHandler* = proc (j: JsonString) {.gcsafe, raises: [].}
4859
SubscriptionErrorHandler* = proc (err: CatchableError) {.gcsafe, raises: [].}
@@ -58,6 +69,10 @@ type
5869
historicalEventsProcessed: bool
5970
removed: bool
6071

72+
ContractInvocation*[TResult, TSender] = object
73+
data*: seq[byte]
74+
sender*: TSender
75+
6176
proc getValue(params: RequestParamsRx, field: string, FieldType: type):
6277
Result[FieldType, string] {.gcsafe, raises: [].} =
6378
try:
@@ -297,23 +312,24 @@ proc send*(web3: Web3, c: EthSend, chainId: ChainId): Future[TxHash] {.async.} =
297312
let t = encodeTransaction(cc, web3.privateKey.get(), chainId)
298313
return await web3.provider.eth_sendRawTransaction(t)
299314

300-
proc sendData(sender: Web3SenderImpl,
315+
proc sendData(web3: Web3,
316+
contractAddress: Address,
317+
defaultAccount: Address,
301318
data: seq[byte],
302319
value: UInt256,
303320
gas: uint64,
304321
gasPrice: int,
305322
chainId = none(ChainId)): Future[TxHash] {.async.} =
306323
let
307-
web3 = sender.web3
308324
gasPrice = if web3.privateKey.isSome() or gasPrice != 0: some(gasPrice.Quantity)
309325
else: none(Quantity)
310326
nonce = if web3.privateKey.isSome(): some(await web3.nextNonce())
311327
else: none(Quantity)
312328

313329
cc = EthSend(
314330
data: data,
315-
`from`: web3.defaultAccount,
316-
to: some(sender.contractAddress),
331+
`from`: defaultAccount,
332+
to: some(contractAddress),
317333
gas: some(Quantity(gas)),
318334
value: some(value),
319335
nonce: nonce,
@@ -329,36 +345,41 @@ proc send*[T](c: ContractInvocation[T, Web3SenderImpl],
329345
value = 0.u256,
330346
gas = 3000000'u64,
331347
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)
333349

334350
proc send*[T](c: ContractInvocation[T, Web3SenderImpl],
335351
chainId: ChainId,
336352
value = 0.u256,
337353
gas = 3000000'u64,
338354
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))
340356

341-
proc call*[T](c: ContractInvocation[T, Web3SenderImpl],
357+
proc callAux(web3: Web3,
358+
contractAddress: Address,
359+
defaultAccount: Address,
360+
data: seq[byte],
342361
value = 0.u256,
343362
gas = 3000000'u64,
344-
blockNumber = high(uint64)): Future[T] {.async.} =
345-
let web3 = c.sender.web3
363+
blockNumber = high(uint64)): Future[seq[byte]] {.async.} =
346364
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)
350368
cc.gas = some(Quantity(gas))
351369
cc.value = some(value)
352-
let response =
370+
result =
353371
if blockNumber != high(uint64):
354372
await web3.provider.eth_call(cc, blockId(blockNumber))
355373
else:
356374
await web3.provider.eth_call(cc, "latest")
357375

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)
358381
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)
362383
else:
363384
raise newException(CatchableError, "No response from the Web3 provider")
364385

@@ -409,6 +430,43 @@ proc exec*[T](c: ContractInvocation[T, Web3SenderImpl], value = 0.u256, gas = 30
409430
proc contractSender*(web3: Web3, T: typedesc, toAddress: Address): Sender[T] =
410431
Sender[T](sender: Web3SenderImpl(web3: web3, contractAddress: toAddress))
411432

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+
412470
proc isDeployed*(s: Sender, atBlock: RtBlockIdentifier): Future[bool] {.async.} =
413471
let
414472
codeFut = case atBlock.kind
@@ -425,3 +483,24 @@ proc isDeployed*(s: Sender, atBlock: RtBlockIdentifier): Future[bool] {.async.}
425483

426484
proc subscribe*[TContract](s: Sender[TContract], t: typedesc, cb: proc): Future[Subscription] {.inline.} =
427485
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)

web3/contract_dsl.nim

+16-18
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,9 @@ import
77
stew/byteutils
88

99
type
10-
ContractInvocation*[TResult, TSender] = object
11-
data*: seq[byte]
12-
sender*: TSender
13-
1410
ContractInstance*[TContract, TSender] = object
1511
sender*: TSender
1612

17-
ContractDeployment*[TContract, TSender] = object
18-
data*: seq[byte]
19-
sender*: TSender
20-
2113
InterfaceObjectKind = enum
2214
function, constructor, event
2315
MutabilityKind = enum
@@ -52,9 +44,6 @@ type
5244
proc keccak256Bytes(s: string): array[32, byte] {.inline.} =
5345
keccak256.digest(s).data
5446

55-
proc initContractInvocation[TSender](TResult: typedesc, sender: TSender, data: seq[byte]): ContractInvocation[TResult, TSender] {.inline.} =
56-
ContractInvocation[TResult, TSender](data: data, sender: sender)
57-
5847
proc joinStrings(s: varargs[string]): string = join(s)
5948

6049
proc unknownType() = discard # Used for informative errors
@@ -192,18 +181,26 @@ proc genFunction(cname: NimNode, functionObject: FunctionObject): NimNode =
192181
funcParamsTuple.add(ident input.name)
193182

194183
result = quote do:
195-
proc `procName`*[TSender](`senderName`: ContractInstance[`cname`, TSender]): ContractInvocation[`output`, TSender] =
184+
proc `procName`*[TSender](`senderName`: ContractInstance[`cname`, TSender]): auto =
196185
discard
197186
for input in functionObject.inputs:
198187
result[3].add nnkIdentDefs.newTree(
199188
ident input.name,
200189
input.typ,
201190
newEmptyNode()
202191
)
203-
result[6] = quote do:
204-
return initContractInvocation(
205-
`output`, `senderName`.sender,
206-
static(keccak256Bytes(`signature`)[0..<4]) & encode(`funcParamsTuple`))
192+
if functionObject.stateMutability == view:
193+
result[6] = quote do:
194+
mixin createImmutableContractInvocation
195+
return createImmutableContractInvocation(
196+
`senderName`.sender, `output`,
197+
static(keccak256Bytes(`signature`)[0..<4]) & encode(`funcParamsTuple`))
198+
else:
199+
result[6] = quote do:
200+
mixin createMutableContractInvocation
201+
return createMutableContractInvocation(
202+
`senderName`.sender, `output`,
203+
static(keccak256Bytes(`signature`)[0..<4]) & encode(`funcParamsTuple`))
207204

208205
proc `&`(a, b: openarray[byte]): seq[byte] =
209206
let sza = a.len
@@ -224,7 +221,7 @@ proc genConstructor(cname: NimNode, constructorObject: ConstructorObject): NimNo
224221
funcParamsTuple.add(ident input.name)
225222

226223
result = quote do:
227-
proc deployContract*[TSender](`sender`: TSender, contractType: typedesc[`cname`], `contractCode`: openarray[byte]): ContractDeployment[`cname`, TSender] =
224+
proc deployContract*[TSender](`sender`: TSender, contractType: typedesc[`cname`], `contractCode`: openarray[byte]): auto =
228225
discard
229226
for input in constructorObject.inputs:
230227
result[3].add nnkIdentDefs.newTree(
@@ -233,7 +230,8 @@ proc genConstructor(cname: NimNode, constructorObject: ConstructorObject): NimNo
233230
newEmptyNode()
234231
)
235232
result[6] = quote do:
236-
return ContractDeployment[`cname`, TSender](data: `contractCode` & encode(`funcParamsTuple`), sender: `sender`)
233+
mixin createContractDeployment
234+
return createContractDeployment(`sender`, `cname`, `contractCode` & encode(`funcParamsTuple`))
237235

238236
proc genEvent(cname: NimNode, eventObject: EventObject): NimNode =
239237
if not eventObject.anonymous:

0 commit comments

Comments
 (0)