Skip to content

Instantly share code, notes, and snippets.

@axic
Last active December 15, 2021 03:14
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save axic/16158c5c88fbc7b1d09dfa8c658bc363 to your computer and use it in GitHub Desktop.
Save axic/16158c5c88fbc7b1d09dfa8c658bc363 to your computer and use it in GitHub Desktop.
ewasm “WRC20” token contract coding challenge

ewasm “WRC20” token contract coding challenge

This document describes a simple contract in pseudocode and gives a couple of test cases for it.

The challenge is designed to be fairly easy to implement in a language of choice, compiled to wasm and interacted with. It is loosely based on the ERC20 token standard, named "WRC20", where the W stands for WebAssembly.

External interface

The contract has two features:

  • querying the balance of an address
  • transfering tokens

Upon deployment the address 0xeD09375DC6B20050d242d1611af97eE4A6E93CAd (private key: 0xdffca753e40d47521d2dd94fe56b0131051d91df614ef0a5e1c301ba9575c550) should have a balance of 1000000.

The interface is loosely based on the Ethereum contract ABI, but is geared towards non-256-bit systems:

  • has a 32 bit selector, which is the first 32 bits of the keccak-256 hash of the signature
  • has multiple data types, but here we only use address (160 bit bytestring) and uint64 a 64 bit number

The two methods:

  • balance is encoded as keccak256("balance(address):(uint64)")[0, 4] address and returns uint64
  • transfer is encoded as keccak256("transfer(address,uint64)")[0, 4] address value

For test cases see below.

Logic in pseudocode

// main ewasm entry point
fun main() { 
  if (eei_calldatasize() < 4)
    eei_revert(0, 0)
  let selector = eei_calldatacopy(0, 4)
  switch selector {
    case 0x9993021a:
      do_balance()
    case 0x5d359fbd:
      do_transfer()
    default:
      eei_revert(0, 0)
  }
}

fun do_balance() {
  if (eei_calldatasize() != 24)
    eei_revert(0, 0)

  let address = eei_calldatacopy(4, 20)
  // make sure that address is 160 bits here,
  // but storage key is 256 bits so need to pad it somehow
  let balance = eei_storageload(address)
  eei_return(balance)
}

fun do_transfer() {
  if (eei_calldatasize() != 32)
    eei_revert(0, 0)

  let sender = eei_sender()
  let recipient = eei_calldatacopy(4, 20)
  let value = eei_calldatacopy(24, 8)
  let sender_balance = eei_storageload(sender)
  let recipient_balance = eei_storageload(recipient)
  
  if (sender_balance < value)
    eei_revert(0, 0)

  sender_balance -= value
  recipient_balance += value

  eei_storagestore(sender, sender_balance)
  eei_storagestore(recipient, recipient_balance)
}

Test cases

  1. Query balance of 0xeD09375DC6B20050d242d1611af97eE4A6E93CAd

Input: 9993021aed09375dc6b20050d242d1611af97ee4a6e93cad

Output: 00000000000f4240

  1. Transfer 500000 to 0xe929CF2544363bdCEE4a976515d5F97758Ef476c (sender as above)

Input: 5d359fbde929cf2544363bdcee4a976515d5f97758ef476c7a120

Output: empty

  1. Query balance of 0xeD09375DC6B20050d242d1611af97eE4A6E93CAd

Input: 9993021aed09375dc6b20050d242d1611af97ee4a6e93cad

Output: 000000000007a120

  1. Query balance of 0xe929CF2544363bdCEE4a976515d5F97758Ef476c

Input: 9993021ae929cf2544363bdcee4a976515d5f97758ef476c

Output: 000000000007a120

@cdetrio
Copy link

cdetrio commented Jun 27, 2018

The initial Rust version is at ewasm/rust-ewasm#4

The workflow is:

  1. run make
  2. run make all
  3. open up ewasm_token.wat and move the line (table $T0 0 anyfunc) from the bottom to the line just before (func $main (export "main") (type $t4).
  4. edit all the import statements e.g. (import "env" "callDataCopy" (func $env.callDataCopy (type $t0))) and change env to ethereum like (import "ethereum" "callDataCopy" (func $env.callDataCopy (type $t0)))
  5. Copy the edited wat code and paste it into wat2wasm. Then click download to download the binary .wasm file.
  6. copy the downloaded wasm file to ewasm_token_env_ethereum.wasm and run bin2hex.js with node bin2hex.js to print the hex bytes.
  7. copy the hex bytes and paste it as contract code in the genesis file.
  8. run cpp-eth to launch a private ewasm testnet chain, and send a transaction to the contract address.

@lrettig
Copy link

lrettig commented Jun 27, 2018

And the input to (4) should be 9993021ae929cf2544363bdcee4a976515d5f97758ef476c (modulo endianness issues) - not sure where the last 3.5 bytes came from.

@ligi
Copy link

ligi commented Jul 12, 2018

I wonder a bit about keccak256("balance(address):(uint64)")[0, 4] address

Usually the return type is not part of the function signature - is this different in eWASM? Would love a link with some more information about this - could not find anything so far.

@axic
Copy link
Author

axic commented May 9, 2019

Usually the return type is not part of the function signature - is this different in eWASM?

Yes, this was trying to fix a shortcoming of the ABI.

@axic
Copy link
Author

axic commented May 9, 2019

Alternatively to maintain tooling compatibility we could use the standard "Contract ABI" for ERC20 with the only change that balances are uint64 and not uint256.

In this case values would be 256-bit long, but since the type is uint64 when parsing in a contract it could ignore the leading zeroes and could just returned leading zeroes without having bignum support.

@jwasinger
Copy link

Usually the return type is not part of the function signature - is this different in eWASM?

Yes, this was trying to fix a shortcoming of the ABI.

@axic can you explain this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment