-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add C example, handwritten example, and generic tester #5
base: master
Are you sure you want to change the base?
Changes from 6 commits
d366157
a27f8aa
11dc6bf
905163f
5c12adf
fb1b2b3
3553ea7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.PHONY: all | ||
|
||
#LLVM_BUILD_DIR=/path/to/llvm-build | ||
LLVM_BUILD_DIR=/home/user/repos/benchmarking/wasmception/build/llvm | ||
|
||
all: | ||
$(LLVM_BUILD_DIR)/bin/clang -cc1 -Ofast -emit-llvm -triple=wasm32-unknown-unknown-wasm wrc20.c | ||
$(LLVM_BUILD_DIR)/bin/llc -O3 -filetype=obj wrc20.ll -o wrc20.o | ||
$(LLVM_BUILD_DIR)/bin/wasm-ld --no-entry wrc20.o -o wrc20.wasm --strip-all -allow-undefined-file=wrc20.syms -export=_main | ||
rm wrc20.ll wrc20.o | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
To see how to convert `wrc20.wasm` to be meet ewasm requirements, see the tutorial https://github.com/ewasm/testnet/blob/e86d665364adfde7232feaaae178c31543734ff2/c_cpp_guide.md . |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
|
||
|
||
// to avoid includes from libc, just hard-code things | ||
typedef unsigned char uint8_t; | ||
typedef int uint32_t; | ||
typedef unsigned long long uint64_t; | ||
|
||
|
||
// types used for Ethereum stuff | ||
typedef uint8_t* bytes; // an array of bytes with unrestricted length | ||
typedef uint8_t bytes32[32]; // an array of 32 bytes | ||
typedef uint8_t address[20]; // an array of 20 bytes | ||
typedef unsigned __int128 u128; // a 128 bit number, represented as a 16 bytes long little endian unsigned integer in memory | ||
//typedef uint256_t u256; // a 256 bit number, represented as a 32 bytes long little endian unsigned integer in memory | ||
typedef uint32_t i32; // same as i32 in WebAssembly | ||
typedef uint32_t i32ptr; // same as i32 in WebAssembly, but treated as a pointer to a WebAssembly memory offset | ||
typedef uint64_t i64; // same as i64 in WebAssembly | ||
|
||
// functions for ethereum stuff | ||
void useGas(i64 amount); | ||
void getCaller(i32ptr* resultOffset); | ||
// memory offset to load the address into (address) | ||
i32 getCallDataSize(); | ||
void callDataCopy(i32ptr* resultOffset, i32 dataOffset, i32 length); | ||
// memory offset to load data into (bytes), the offset in the input data, the length of data to copy | ||
void revert(i32ptr* dataOffset, i32 dataLength); | ||
void finish(i32ptr* dataOffset, i32 dataLength); | ||
void storageStore(i32ptr* pathOffset, i32ptr* resultOffset); | ||
void storageLoad(i32ptr* pathOffset, i32ptr* resultOffset); | ||
//the memory offset to load the path from (bytes32), the memory offset to store/load the result at (bytes32) | ||
void printMemHex(i32ptr* offset, i32 length); | ||
void printStorageHex(i32ptr* key); | ||
|
||
|
||
i64 reverse_bytes(i64 a){ | ||
i64 b = 0; | ||
b += (a & 0xff00000000000000)>>56; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool! But Wasm has no bswap opcode. I would have to look at the output wasm, but don't think there is a difference since addition + and or | both use the ALU in one cycle. |
||
b += (a & 0x00ff000000000000)>>40; | ||
b += (a & 0x0000ff0000000000)>>24; | ||
b += (a & 0x000000ff00000000)>>8; | ||
b += (a & 0x00000000ff000000)<<8; | ||
b += (a & 0x0000000000ff0000)<<24; | ||
b += (a & 0x000000000000ff00)<<40; | ||
b += (a & 0x00000000000000ff)<<56; | ||
return b; | ||
} | ||
|
||
|
||
// global data used in next function, will be allocated to WebAssembly memory | ||
bytes32 addy[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; | ||
bytes32 balance[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; | ||
|
||
void do_balance() { | ||
if (getCallDataSize() != 24) | ||
revert(0, 0); | ||
|
||
// get address to check balance of, note: padded to 32 bytes since used as key | ||
callDataCopy((i32ptr*)addy, 4, 20); | ||
|
||
// get balance | ||
storageLoad((i32ptr*)addy, (i32ptr*)balance); | ||
|
||
// return balance | ||
finish((i32ptr*)balance, 32); | ||
} | ||
|
||
|
||
// global data used in next function, will be allocated to WebAssembly memory | ||
bytes32 sender[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; | ||
bytes32 recipient[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; | ||
bytes32 value[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; | ||
bytes32 recipient_balance[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; | ||
bytes32 sender_balance[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; | ||
|
||
void do_transfer() { | ||
if (getCallDataSize() != 32) | ||
revert(0, 0); | ||
|
||
// get caller | ||
getCaller((i32ptr*)sender); | ||
|
||
// get recipient from message at byte 4, length 20 | ||
callDataCopy((i32ptr*)recipient, 4, 20); | ||
|
||
// get amount to transfer from message at byte 24, length 8 | ||
callDataCopy((i32ptr*)value, 24, 8); | ||
*(i64*)value = reverse_bytes(*(i64*)value); | ||
|
||
// get balances | ||
storageLoad((i32ptr*)sender, (i32ptr*)sender_balance); | ||
storageLoad((i32ptr*)recipient, (i32ptr*)recipient_balance); | ||
*(i64*)sender_balance = reverse_bytes(*(i64*)sender_balance); | ||
*(i64*)recipient_balance = reverse_bytes(*(i64*)recipient_balance); | ||
|
||
// make sure sender has enough | ||
if (*(i64*)sender_balance < *(i64*)value) | ||
revert(0, 0); | ||
|
||
// adjust balances | ||
* (i64*)sender_balance -= * (i64*)value; | ||
* (i64*)recipient_balance += * (i64*)value; | ||
|
||
// store results | ||
*(i64*)sender_balance = reverse_bytes(*(i64*)sender_balance); | ||
*(i64*)recipient_balance = reverse_bytes(*(i64*)recipient_balance); | ||
storageStore((i32ptr*)sender, (i32ptr*)sender_balance); | ||
storageStore((i32ptr*)recipient, (i32ptr*)recipient_balance); | ||
|
||
} | ||
|
||
|
||
// global data used in next function, will be allocated to WebAssembly memory | ||
i32 selector[1] = {0}; | ||
|
||
void _main(void) { | ||
if (getCallDataSize() < 4) | ||
revert(0, 0); | ||
|
||
// get first four bytes of message, use for calling | ||
callDataCopy((i32ptr*)selector, 0, 4); //from byte 0, length 4 | ||
|
||
// call function based on selector value | ||
switch (*selector) { | ||
case 0x1a029399: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious, why are the selector values here different from those in the example pseudo-code for this repository? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that you are referring to the wrc20 spec https://gist.github.com/axic/16158c5c88fbc7b1d09dfa8c658bc363 where the selector has value |
||
do_balance(); | ||
break; | ||
case 0xbd9f355d: | ||
do_transfer(); | ||
break; | ||
default: | ||
revert(0, 0); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
useGas | ||
getCaller | ||
getCallDataSize | ||
callDataCopy | ||
revert | ||
finish | ||
storageStore | ||
storageLoad | ||
printMemHex | ||
printStorageHex |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
(module | ||
(func $revert (import "ethereum" "revert") (param i32 i32)) | ||
(func $finish (import "ethereum" "finish") (param i32 i32)) | ||
(func $getCallDataSize (import "ethereum" "getCallDataSize") (result i32)) | ||
(func $callDataCopy (import "ethereum" "callDataCopy") (param i32 i32 i32)) | ||
(func $storageLoad (import "ethereum" "storageLoad") (param i32 i32)) | ||
(func $storageStore (import "ethereum" "storageStore") (param i32 i32)) | ||
(func $getCaller (import "ethereum" "getCaller") (param i32)) | ||
(memory (export "memory") 1) | ||
(func (export "main") | ||
block | ||
block | ||
call $getCallDataSize | ||
i32.const 4 | ||
i32.ge_u | ||
br_if 0 | ||
i32.const 0 | ||
i32.const 0 | ||
call $revert | ||
br 1 | ||
end | ||
i32.const 0 ;;selector, 4 bytes | ||
i32.const 0 | ||
i32.const 4 | ||
call $callDataCopy | ||
block | ||
i32.const 0 ;;load selector | ||
i32.load | ||
i32.const 0x1a029399 | ||
i32.eq | ||
i32.eqz | ||
br_if 0 | ||
call $do_balance | ||
br 1 | ||
end | ||
block | ||
i32.const 0 ;;load selector | ||
i32.load | ||
i32.const 0xbd9f355d | ||
i32.eq | ||
i32.eqz | ||
br_if 0 | ||
call $do_transfer | ||
br 1 | ||
end | ||
i32.const 0 | ||
i32.const 0 | ||
call $revert | ||
end) | ||
(func $do_balance | ||
block | ||
block | ||
call $getCallDataSize | ||
i32.const 24 | ||
i32.eq | ||
br_if 0 | ||
i32.const 0 | ||
i32.const 0 | ||
call $revert | ||
br 1 | ||
end | ||
i32.const 0 ;;address to bytes 0-31, last 12 bytes are 0-padded | ||
i32.const 4 | ||
i32.const 20 | ||
call $callDataCopy | ||
i32.const 0 ;; get token balance of address in bytes 0-31, put in bytes 32-63 | ||
i32.const 32 | ||
call $storageLoad | ||
i32.const 32 ;; return first 8 bytes of balance | ||
i32.const 8 | ||
call $finish | ||
end) | ||
(func $do_transfer | ||
(local i64 i64 i64) ;;sender_balance, recipient_balance, value | ||
block | ||
block | ||
call $getCallDataSize | ||
i32.const 32 | ||
i32.eq | ||
br_if 0 | ||
i32.const 0 | ||
i32.const 0 | ||
call $revert | ||
br 1 | ||
end | ||
;; memory bytes 0 32 64 | ||
;; senderAddy recipientAddy tmpForTokenValues | ||
i32.const 0 ;;sender address to bytes 0-19 (storage key uses bytes 0-31) | ||
call $getCaller | ||
i32.const 32 ;;recipient address to bytes 32-51 (storage key uses bytes 32-63) | ||
i32.const 4 | ||
i32.const 20 | ||
call $callDataCopy | ||
i32.const 64 ;;temporarily put transfer_value in bytes 64-71, reverse 8 msb, put in in local 0 | ||
i32.const 24 | ||
i32.const 8 | ||
call $callDataCopy | ||
i32.const 64 | ||
i64.load | ||
call $i64.reverse_bytes | ||
set_local 0 | ||
i32.const 0 ;;temporarily put sender_balance into bytes 64-95, reverse 8 msb, put it in local 1 | ||
i32.const 64 | ||
call $storageLoad | ||
i32.const 64 | ||
i64.load | ||
call $i64.reverse_bytes | ||
set_local 1 | ||
i32.const 32 ;;temporarily put recipient_balance into bytes 64-95, reverse 8 msb, put in local 2 | ||
i32.const 64 | ||
call $storageLoad | ||
i32.const 64 | ||
i64.load | ||
call $i64.reverse_bytes | ||
set_local 2 | ||
block ;; if transver_value < sender_balance, then revert | ||
get_local 0 | ||
get_local 1 | ||
i64.le_u | ||
br_if 0 | ||
i32.const 0 | ||
i32.const 0 | ||
call $revert | ||
br 1 | ||
end | ||
get_local 1 ;;sender_balance -= value | ||
get_local 0 | ||
i64.sub | ||
set_local 1 | ||
get_local 2 ;;recipient_balance += value | ||
get_local 0 | ||
i64.add | ||
set_local 2 | ||
i32.const 64 ;;reverse sender_balance, write to memory, put in storage | ||
get_local 1 | ||
call $i64.reverse_bytes | ||
i64.store | ||
i32.const 0 | ||
i32.const 64 | ||
call $storageStore | ||
i32.const 64 ;;reverse recipient_balance, write to memory, put in storage | ||
get_local 2 | ||
call $i64.reverse_bytes | ||
i64.store | ||
i32.const 32 | ||
i32.const 64 | ||
call $storageStore | ||
end) | ||
(func $i64.reverse_bytes (param i64) (result i64) | ||
(local i64 i64) ;;iter variable, val to return | ||
block | ||
loop | ||
get_local 1 ;;iter variable | ||
i64.const 8 | ||
i64.ge_u | ||
br_if 1 | ||
get_local 0 ;;original | ||
i64.const 56 ;;shift left | ||
get_local 1 | ||
i64.const 8 | ||
i64.mul | ||
i64.sub | ||
i64.shl | ||
i64.const 56 ;;shift right | ||
i64.shr_u | ||
i64.const 56 ;;shift left | ||
i64.const 8 | ||
get_local 1 | ||
i64.mul | ||
i64.sub | ||
i64.shl | ||
get_local 2 ;;update | ||
i64.add | ||
set_local 2 | ||
get_local 1 ;;iter+=1 | ||
i64.const 1 | ||
i64.add | ||
set_local 1 | ||
br 0 | ||
end | ||
end | ||
get_local 2 | ||
) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the upstream LLVM has wasm target enabled by default. How do you want to handle the LLVM installation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is old and outdated. I don't want this repo to include the wrc20.c code anymore. I think that this PR should be closed, but am waiting for feedback before I close.
This is a great question which I need advice for in the current wrc20.c repo https://github.com/poemm/C_ewasm_contracts . It is good that newer version of ubuntu have LLVM 8+ by default. But getting LLVM 8+ may be a challenge for some users. The linked repo gives instructions in the README and compilation commands in the Makefile, but, in my experience, I have had to restart the compilation several times before it finished. Maybe this conversation should be moved to that repo, but I am happy to continue here.