Skip to content

Instantly share code, notes, and snippets.

@axic axic/README.md
Last active Jul 17, 2019

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

cdetrio commented Jun 26, 2018

The input for 2. should be 0x5d359fbde929cf2544363bdcee4a976515d5f97758ef476c07a120. The 2nd argument, amount to transfer, should be 07a120 rather than 7a120.

edit: actually the input should be 0x5d359fbde929cf2544363bdcee4a976515d5f97758ef476c000000000007a120 (value should be 32 bytes).

@cdetrio

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Owner 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

This comment has been minimized.

Copy link
Owner 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

This comment has been minimized.

Copy link

jwasinger commented Jun 1, 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 can you explain this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.