From 01b5554d93ee2d16f58664c15e36f2bc139c3c48 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Sun, 21 Apr 2019 19:33:03 -0600 Subject: [PATCH 1/7] wrc20 updates by poemm * endian fixes * work with wrc20 test suite --- examples/wrc20.nim | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/examples/wrc20.nim b/examples/wrc20.nim index 3856b4e..ef18716 100644 --- a/examples/wrc20.nim +++ b/examples/wrc20.nim @@ -1,27 +1,30 @@ ## ewasm “WRC20” token contract coding challenge ## https://gist.github.com/axic/16158c5c88fbc7b1d09dfa8c658bc363 +## updated by poemm here: +## https://discuss.status.im/t/wrc20-and-nim-the-ewasm-token-challenge/1167/11 + import ../eth_contracts, endians proc do_balance() = if getCallDataSize() != 24: revert(nil, 0) - var address{.noinit.}: array[20, byte] - callDataCopy(address, 4) + var address{.noinit.}: array[32, byte] + callDataCopy(addr address, 4, 20) var balance{.noinit.}: array[32, byte] storageLoad(address, addr balance) - finish(addr balance, sizeof(balance).int32) + finish(addr balance, 8) proc do_transfer() = if getCallDataSize() != 32: revert(nil, 0) - var sender: array[20, byte] + var sender: array[32, byte] getCaller(addr sender) - var recipient: array[20, byte] - callDataCopy(recipient, 4) + var recipient: array[32, byte] + callDataCopy(addr recipient, 4, 20) var value: array[8, byte] callDataCopy(value, 24) @@ -33,18 +36,18 @@ proc do_transfer() = var sb, rb, v: uint64 bigEndian64(addr v, addr value) - bigEndian64(addr sb, addr senderBalance[32 - 8]) + bigEndian64(addr sb, addr senderBalance[0]) if sb < v: revert(nil, 0) - bigEndian64(addr rb, addr recipientBalance[32 - 8]) + bigEndian64(addr rb, addr recipientBalance[0]) sb -= v rb += v # TODO there's an overflow possible here.. - bigEndian64(addr senderBalance[32 - 8], addr sb) - bigEndian64(addr recipientBalance[32 - 8], addr rb) + bigEndian64(addr senderBalance[0], addr sb) + bigEndian64(addr recipientBalance[0], addr rb) storageStore(sender, addr senderBalance) storageStore(recipient, addr recipientBalance) @@ -55,9 +58,9 @@ proc main() {.exportwasm.} = var selector: uint32 callDataCopy(selector, 0) case selector - of 0x9993021a'u32: + of 0x1a029399'u32: do_balance() - of 0x5d359fbd'u32: + of 0xbd9f355d'u32: do_transfer() else: revert(nil, 0) From 465a08a085472007df1c4dfc2ea5b868bd3b1e91 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Sun, 21 Apr 2019 20:48:29 -0600 Subject: [PATCH 2/7] simplify wrc20 * replace bigendian64 with native version * sprinkle some noinits * be a bit more typeful when loading things --- eth_contracts.nim | 28 ++++++++++------------- examples/wrc20.nim | 56 +++++++++++++++++++++++++++++++--------------- 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/eth_contracts.nim b/eth_contracts.nim index e849248..2e76fab 100644 --- a/eth_contracts.nim +++ b/eth_contracts.nim @@ -267,27 +267,23 @@ proc getBlockTimestamp*(): int64 proc callDataCopy*[T](res: var T, offset: int) {.inline.} = callDataCopy(addr res, offset.int32, sizeof(res).int32) -proc storageLoad*[N](path: array[N, byte], res: pointer) {.inline.} = +proc storageLoad*[N](path: array[N, byte], res: var array[32, byte]) {.inline.} = when path.len < 32: - type PaddedPath {.packed.} = object - padding: array[32 - path.len, byte] - p: array[N, byte] - var p: PaddedPath - p.p = path - storageLoad(addr p, res) + var padded {.noinit.}: array[32, byte] + copyMem(addr padded[0], unsafeAddr path, path.len) + zeroMem(addr padded[path.len], padded.len - path.len) + storageLoad(addr padded, addr res[0]) else: - storageLoad(unsafeAddr path[0], res) + storageLoad(unsafeAddr path[0], addr res[0]) -proc storageStore*[N](path: array[N, byte], res: pointer) {.inline.} = +proc storageStore*[N](path: array[N, byte], res: array[32, byte]) {.inline.} = when path.len < 32: - type PaddedPath {.packed.} = object - padding: array[32 - path.len, byte] - p: array[N, byte] - var p: PaddedPath - p.p = path - storageStore(addr p, res) + var padded {.noinit.}: array[32, byte] + copyMem(addr padded[0], unsafeAddr path, path.len) + zeroMem(addr padded[path.len], padded.len - path.len) + storageStore(addr padded, unsafeAddr res) else: - storageStore(unsafeAddr path[0], res) + storageStore(unsafeAddr path[0], unsafeAddr res) macro exportwasm*(p: untyped): untyped = expectKind(p, nnkProcDef) diff --git a/examples/wrc20.nim b/examples/wrc20.nim index ef18716..21254a6 100644 --- a/examples/wrc20.nim +++ b/examples/wrc20.nim @@ -4,7 +4,27 @@ ## updated by poemm here: ## https://discuss.status.im/t/wrc20-and-nim-the-ewasm-token-challenge/1167/11 -import ../eth_contracts, endians +import ../eth_contracts + +proc bigEndian64*(inp: pointer): uint64 = + # If we turn on the llvm inliner, it will inline calls to the bswap intrinsic + # causing a code size explosion - looks like for the wasm target, it doesn't + # count expansion that's done due to the lack of a bswap instruction. + # As a workaround, one needs to make sure that inlining is disabled when + # compiling with llvm / clang! + # Also as a workaround, we do a special version of bigEndian64 here that + # avoids some random calls that are generally needed for byte alignment but + # can be avoided here.. + # TODO report upstream + var x = cast[ptr uint64](inp)[] + + x = (x and 0x00000000FFFFFFFF'u64) shl 32'u64 or (x and 0xFFFFFFFF00000000'u64) shr 32'u64 + x = (x and 0x0000FFFF0000FFFF'u64) shl 16'u64 or (x and 0xFFFF0000FFFF0000'u64) shr 16'u64 + x = (x and 0x00FF00FF00FF00FF'u64) shl 8'u64 or (x and 0xFF00FF00FF00FF00'u64) shr 8'u64 + x + +template bigEndian64*(v: uint64, outp: var openArray[byte]) = + cast[ptr uint64](addr outp[0])[] = bigEndian64(unsafeAddr v) proc do_balance() = if getCallDataSize() != 24: @@ -14,43 +34,43 @@ proc do_balance() = callDataCopy(addr address, 4, 20) var balance{.noinit.}: array[32, byte] - storageLoad(address, addr balance) + storageLoad(address, balance) finish(addr balance, 8) proc do_transfer() = if getCallDataSize() != 32: revert(nil, 0) - var sender: array[32, byte] + var sender{.noinit.}: array[20, byte] getCaller(addr sender) - var recipient: array[32, byte] - callDataCopy(addr recipient, 4, 20) + var recipient{.noinit.}: array[20, byte] + callDataCopy(recipient, 4) var value: array[8, byte] callDataCopy(value, 24) - var senderBalance: array[32, byte] - storageLoad(sender, addr senderBalance) - var recipientBalance: array[32, byte] - storageLoad(recipient, addr recipientBalance) + var senderBalance{.noinit.}: array[32, byte] + storageLoad(sender, senderBalance) + var recipientBalance{.noinit.}: array[32, byte] + storageLoad(recipient, recipientBalance) - var sb, rb, v: uint64 - - bigEndian64(addr v, addr value) - bigEndian64(addr sb, addr senderBalance[0]) + var + sb = bigEndian64(addr senderBalance) + v = bigEndian64(addr value) if sb < v: revert(nil, 0) - bigEndian64(addr rb, addr recipientBalance[0]) + var + rb = bigEndian64(addr recipientBalance) sb -= v rb += v # TODO there's an overflow possible here.. - bigEndian64(addr senderBalance[0], addr sb) - bigEndian64(addr recipientBalance[0], addr rb) + bigEndian64(sb, senderBalance) + bigEndian64(sb, recipientBalance) - storageStore(sender, addr senderBalance) - storageStore(recipient, addr recipientBalance) + storageStore(sender, senderBalance) + storageStore(recipient, recipientBalance) proc main() {.exportwasm.} = if getCallDataSize() < 4: From bb77f8b1014af66473b45c3a84c177a36c542640 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Sun, 21 Apr 2019 21:28:41 -0600 Subject: [PATCH 3/7] zero-fill when copying data instead of doing extra copy --- eth_contracts.nim | 10 ++++++++++ examples/wrc20.nim | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/eth_contracts.nim b/eth_contracts.nim index 2e76fab..c0fcb4a 100644 --- a/eth_contracts.nim +++ b/eth_contracts.nim @@ -267,6 +267,16 @@ proc getBlockTimestamp*(): int64 proc callDataCopy*[T](res: var T, offset: int) {.inline.} = callDataCopy(addr res, offset.int32, sizeof(res).int32) +proc callDataCopy*[N](res: var array[N, byte], offset: int, bytes: int) {.inline.} = + ## copy bytes from calldata, zeroing out the rest of the array + callDataCopy(addr res[0], offset.int32, res.len.int32) + zeroMem(addr res[bytes], res.len - bytes) + +proc getCaller*[N](res: var array[N, byte]) {.inline.} = + ## copy caller, zeroing out the rest of the array + getCaller(addr res[0]) + zeroMem(addr res[20], res.len - 20) + proc storageLoad*[N](path: array[N, byte], res: var array[32, byte]) {.inline.} = when path.len < 32: var padded {.noinit.}: array[32, byte] diff --git a/examples/wrc20.nim b/examples/wrc20.nim index 21254a6..06f4092 100644 --- a/examples/wrc20.nim +++ b/examples/wrc20.nim @@ -31,7 +31,7 @@ proc do_balance() = revert(nil, 0) var address{.noinit.}: array[32, byte] - callDataCopy(addr address, 4, 20) + callDataCopy(address, 4, 20) var balance{.noinit.}: array[32, byte] storageLoad(address, balance) @@ -41,10 +41,10 @@ proc do_transfer() = if getCallDataSize() != 32: revert(nil, 0) - var sender{.noinit.}: array[20, byte] - getCaller(addr sender) - var recipient{.noinit.}: array[20, byte] - callDataCopy(recipient, 4) + var sender{.noinit.}: array[32, byte] + getCaller(sender) + var recipient{.noinit.}: array[32, byte] + callDataCopy(recipient, 4, 20) var value: array[8, byte] callDataCopy(value, 24) From 5dc727e82a3482189e0b547117ac9ccbaa54e465 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Sun, 30 Jun 2019 13:18:48 +0200 Subject: [PATCH 4/7] wasm code size golfing: down to 685 bytes --- eth_contracts.nim | 5 ++++- examples/Makefile | 34 ++++++++++++++++++++++++++++++++++ examples/config.nims | 22 ---------------------- examples/wrc20.nim | 31 +++++++++++++------------------ 4 files changed, 51 insertions(+), 41 deletions(-) create mode 100644 examples/Makefile delete mode 100644 examples/config.nims diff --git a/eth_contracts.nim b/eth_contracts.nim index c0fcb4a..f4791d2 100644 --- a/eth_contracts.nim +++ b/eth_contracts.nim @@ -267,9 +267,12 @@ proc getBlockTimestamp*(): int64 proc callDataCopy*[T](res: var T, offset: int) {.inline.} = callDataCopy(addr res, offset.int32, sizeof(res).int32) +proc callDataCopy*(T: type, offset: int): T {.inline.} = + callDataCopy(addr result, offset.int32, sizeof(T).int32) + proc callDataCopy*[N](res: var array[N, byte], offset: int, bytes: int) {.inline.} = ## copy bytes from calldata, zeroing out the rest of the array - callDataCopy(addr res[0], offset.int32, res.len.int32) + callDataCopy(addr res[0], offset.int32, bytes.int32) zeroMem(addr res[bytes], res.len - bytes) proc getCaller*[N](res: var array[N, byte]) {.inline.} = diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 0000000..0736453 --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,34 @@ +L=/home/arnetheduck/src/nlvm/ext/llvm-8.0.0.src/sha/bin +NFLAGS=--checks:off --nlvm.target=wasm32-unknown-unknown --gc:none -l:--compress-relocations -l:--no-entry -l:--allow-undefined -l:--strip-all --noMain +B=/home/arnetheduck/src/binaryen/bin + +.PHONY: all +all: wrc20.wat wrc20opt.wat wrc20opt.ll wrc20strip.wat wrc20binaryen.wat + ls -l + +wrc20opt.wasm: wrc20opt.ll + $(L)/llc -filetype=obj -O2 wrc20opt.ll -o wrc20opt.o + $(L)/wasm-ld -o wrc20opt.wasm wrc20opt.o -O2 --strip-all --no-entry --allow-undefined --export-dynamic --compress-relocations + +wrc20.ll: wrc20.nim + nlvm c $(NFLAGS) -c -d:release --opt:size wrc20 + +wrc20.wasm: wrc20.nim + nlvm c $(NFLAGS) -d:release --opt:size wrc20 + +wrc20opt.ll: wrc20.ll + $(L)/opt -O2 -disable-inlining wrc20.ll | $(L)/llvm-dis > wrc20opt.ll + +.PHONY: clean +clean: + rm -f *.wasm *.ll *.wat + +%.wat: %.wasm + wasm2wat --generate-names $< > $@ + +wrc20strip.wasm: wrc20opt.wat + sed -e '/__heap_base\|__data_end\|funcref/d' -e 's/env/ethereum/g' wrc20opt.wat > wrc20tmp.wat + wat2wasm -o wrc20strip.wasm wrc20tmp.wat + +wrc20binaryen.wasm: wrc20strip.wasm + $(B)/wasm-opt -Os -o wrc20binaryen.wasm wrc20strip.wasm diff --git a/examples/config.nims b/examples/config.nims deleted file mode 100644 index 271f97d..0000000 --- a/examples/config.nims +++ /dev/null @@ -1,22 +0,0 @@ ---os:standalone ---cpu:i386 ---cc:clang ---gc:none ---nomain ---opt:speed - -let llBin = getEnv("WASM_LLVM_BIN") -if llBin.len == 0: - raise newException(Exception, "WASM_LLVM_BIN env var is not set") - -let llTarget = "wasm32-unknown-unknown-wasm" - -switch("passC", "--target=" & llTarget) -switch("passL", "--target=" & llTarget) - -switch("passC", "-I./include") - - -switch("clang.exe", llBin & "/clang") -switch("clang.linkerexe", llBin & "/clang") -switch("clang.options.linker", "-nostdlib -Wl,--no-entry,--allow-undefined,--strip-all") diff --git a/examples/wrc20.nim b/examples/wrc20.nim index 06f4092..7ce4da8 100644 --- a/examples/wrc20.nim +++ b/examples/wrc20.nim @@ -6,25 +6,20 @@ import ../eth_contracts -proc bigEndian64*(inp: pointer): uint64 = - # If we turn on the llvm inliner, it will inline calls to the bswap intrinsic - # causing a code size explosion - looks like for the wasm target, it doesn't - # count expansion that's done due to the lack of a bswap instruction. - # As a workaround, one needs to make sure that inlining is disabled when - # compiling with llvm / clang! - # Also as a workaround, we do a special version of bigEndian64 here that - # avoids some random calls that are generally needed for byte alignment but - # can be avoided here.. - # TODO report upstream - var x = cast[ptr uint64](inp)[] - - x = (x and 0x00000000FFFFFFFF'u64) shl 32'u64 or (x and 0xFFFFFFFF00000000'u64) shr 32'u64 +proc bigEndian64*(x: uint64): uint64 {.noinline.} = + # If we use inliner or enable the bswap intrinsic, code size explodes as wasm + # lacks a bswap instruction + var x = (x and 0x00000000FFFFFFFF'u64) shl 32'u64 or (x and 0xFFFFFFFF00000000'u64) shr 32'u64 x = (x and 0x0000FFFF0000FFFF'u64) shl 16'u64 or (x and 0xFFFF0000FFFF0000'u64) shr 16'u64 x = (x and 0x00FF00FF00FF00FF'u64) shl 8'u64 or (x and 0xFF00FF00FF00FF00'u64) shr 8'u64 x template bigEndian64*(v: uint64, outp: var openArray[byte]) = - cast[ptr uint64](addr outp[0])[] = bigEndian64(unsafeAddr v) + cast[ptr uint64](addr outp[0])[] = bigEndian64(v) + +template bigEndian64*[N: static int](v: array[N, byte]): uint64 = + static: assert N >= sizeof(uint64) + bigEndian64(cast[ptr uint64](addr v[0])[]) proc do_balance() = if getCallDataSize() != 24: @@ -54,20 +49,20 @@ proc do_transfer() = storageLoad(recipient, recipientBalance) var - sb = bigEndian64(addr senderBalance) - v = bigEndian64(addr value) + sb = bigEndian64(senderBalance) + v = bigEndian64(value) if sb < v: revert(nil, 0) var - rb = bigEndian64(addr recipientBalance) + rb = bigEndian64(recipientBalance) sb -= v rb += v # TODO there's an overflow possible here.. bigEndian64(sb, senderBalance) - bigEndian64(sb, recipientBalance) + bigEndian64(rb, recipientBalance) storageStore(sender, senderBalance) storageStore(recipient, recipientBalance) From 9974d52d53f941fad34a702e5fd56f47ef223a27 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Sun, 30 Jun 2019 14:59:33 +0200 Subject: [PATCH 5/7] more code golfing and simplification --- examples/Makefile | 2 +- examples/wrc20.nim | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index 0736453..cb8eb4a 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -17,7 +17,7 @@ wrc20.wasm: wrc20.nim nlvm c $(NFLAGS) -d:release --opt:size wrc20 wrc20opt.ll: wrc20.ll - $(L)/opt -O2 -disable-inlining wrc20.ll | $(L)/llvm-dis > wrc20opt.ll + $(L)/opt -Oz wrc20.ll | $(L)/llvm-dis > wrc20opt.ll .PHONY: clean clean: diff --git a/examples/wrc20.nim b/examples/wrc20.nim index 7ce4da8..21f552e 100644 --- a/examples/wrc20.nim +++ b/examples/wrc20.nim @@ -9,9 +9,9 @@ import ../eth_contracts proc bigEndian64*(x: uint64): uint64 {.noinline.} = # If we use inliner or enable the bswap intrinsic, code size explodes as wasm # lacks a bswap instruction - var x = (x and 0x00000000FFFFFFFF'u64) shl 32'u64 or (x and 0xFFFFFFFF00000000'u64) shr 32'u64 - x = (x and 0x0000FFFF0000FFFF'u64) shl 16'u64 or (x and 0xFFFF0000FFFF0000'u64) shr 16'u64 - x = (x and 0x00FF00FF00FF00FF'u64) shl 8'u64 or (x and 0xFF00FF00FF00FF00'u64) shr 8'u64 + var x = (x and 0x00000000FFFFFFFF'u64) shl 32 or (x and 0xFFFFFFFF00000000'u64) shr 32 + x = (x and 0x0000FFFF0000FFFF'u64) shl 16 or (x and 0xFFFF0000FFFF0000'u64) shr 16 + x = (x and 0x00FF00FF00FF00FF'u64) shl 8 or (x and 0xFF00FF00FF00FF00'u64) shr 8 x template bigEndian64*(v: uint64, outp: var openArray[byte]) = From d9ce554caa612cf761dce5999309d7862fbe0ec3 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Sun, 30 Jun 2019 15:07:30 +0200 Subject: [PATCH 6/7] even more code golfing --- examples/wrc20.nim | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/examples/wrc20.nim b/examples/wrc20.nim index 21f552e..1ce90b4 100644 --- a/examples/wrc20.nim +++ b/examples/wrc20.nim @@ -36,17 +36,17 @@ proc do_transfer() = if getCallDataSize() != 32: revert(nil, 0) - var sender{.noinit.}: array[32, byte] - getCaller(sender) - var recipient{.noinit.}: array[32, byte] - callDataCopy(recipient, 4, 20) - var value: array[8, byte] - callDataCopy(value, 24) + var + sender{.noinit.}: array[32, byte] + senderBalance{.noinit.}: array[32, byte] + value{.noinit.}: array[8, byte] + recipient{.noinit.}: array[32, byte] + recipientBalance{.noinit.}: array[32, byte] - var senderBalance{.noinit.}: array[32, byte] + getCaller(sender) storageLoad(sender, senderBalance) - var recipientBalance{.noinit.}: array[32, byte] - storageLoad(recipient, recipientBalance) + + callDataCopy(value, 24) var sb = bigEndian64(senderBalance) @@ -55,6 +55,9 @@ proc do_transfer() = if sb < v: revert(nil, 0) + callDataCopy(recipient, 4, 20) + storageLoad(recipient, recipientBalance) + var rb = bigEndian64(recipientBalance) @@ -62,9 +65,9 @@ proc do_transfer() = rb += v # TODO there's an overflow possible here.. bigEndian64(sb, senderBalance) - bigEndian64(rb, recipientBalance) - storageStore(sender, senderBalance) + + bigEndian64(rb, recipientBalance) storageStore(recipient, recipientBalance) proc main() {.exportwasm.} = From 5a0903ecb4ceca1b77e0d3105c32f237a0d0a5e4 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Sun, 30 Jun 2019 17:34:26 +0200 Subject: [PATCH 7/7] more golfing, 659 bytes :) --- eth_contracts.nim | 2 +- examples/wrc20.nim | 25 ++++++++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/eth_contracts.nim b/eth_contracts.nim index f4791d2..136830c 100644 --- a/eth_contracts.nim +++ b/eth_contracts.nim @@ -267,7 +267,7 @@ proc getBlockTimestamp*(): int64 proc callDataCopy*[T](res: var T, offset: int) {.inline.} = callDataCopy(addr res, offset.int32, sizeof(res).int32) -proc callDataCopy*(T: type, offset: int): T {.inline.} = +proc callDataCopy*(T: type, offset: int): T {.inline, noinit.} = callDataCopy(addr result, offset.int32, sizeof(T).int32) proc callDataCopy*[N](res: var array[N, byte], offset: int, bytes: int) {.inline.} = diff --git a/examples/wrc20.nim b/examples/wrc20.nim index 1ce90b4..92b744b 100644 --- a/examples/wrc20.nim +++ b/examples/wrc20.nim @@ -39,43 +39,42 @@ proc do_transfer() = var sender{.noinit.}: array[32, byte] senderBalance{.noinit.}: array[32, byte] - value{.noinit.}: array[8, byte] - recipient{.noinit.}: array[32, byte] - recipientBalance{.noinit.}: array[32, byte] getCaller(sender) storageLoad(sender, senderBalance) - callDataCopy(value, 24) - var sb = bigEndian64(senderBalance) - v = bigEndian64(value) + v = bigEndian64(uint64.callDataCopy(24)) if sb < v: revert(nil, 0) + sb -= v + + bigEndian64(sb, senderBalance) + storageStore(sender, senderBalance) + + var + recipient{.noinit.}: array[32, byte] + recipientBalance{.noinit.}: array[32, byte] + callDataCopy(recipient, 4, 20) storageLoad(recipient, recipientBalance) var rb = bigEndian64(recipientBalance) - sb -= v rb += v # TODO there's an overflow possible here.. - bigEndian64(sb, senderBalance) - storageStore(sender, senderBalance) - bigEndian64(rb, recipientBalance) storageStore(recipient, recipientBalance) proc main() {.exportwasm.} = if getCallDataSize() < 4: revert(nil, 0) - var selector: uint32 - callDataCopy(selector, 0) - case selector + + case uint32.callDataCopy(0) of 0x1a029399'u32: do_balance() of 0xbd9f355d'u32: