Skip to content

Commit

Permalink
implement Hofmann structure (#169)
Browse files Browse the repository at this point in the history
* WIP: balanceOf working test

* Add readData.

* Attempt transfer implementation. Broken commit.

* Refactor a bit.

* Transfer test now passing.

* Some cleanup.

* Uncomment tests.

* Linting...

* Some more linting ...

* Fix off-chain stepper.

* Cosmetics.

* Indentation.
  • Loading branch information
TheReturnOfJan authored and johannbarbie committed Jan 13, 2020
1 parent f4c49af commit b060a2f
Show file tree
Hide file tree
Showing 13 changed files with 823 additions and 213 deletions.
5 changes: 5 additions & 0 deletions contracts/EVMConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,11 @@ contract EVMConstants {
uint16 constant internal GAS_EC_ADD = 500;
uint16 constant internal GAS_EC_MUL = 40000;

// Func sigs
bytes4 constant internal FUNCSIG_TRANSFER = 0xa9059cbb;
bytes4 constant internal FUNCSIG_BALANCEOF = 0x70a08231;
bytes4 constant internal FUNCSIG_READATA = 0x37ebbc03;

// ERRORS

uint8 constant internal NO_ERROR = 0;
Expand Down
116 changes: 113 additions & 3 deletions contracts/EVMRuntime.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,24 @@ import { EVMMemory } from "./EVMMemory.slb";
import { EVMStack } from "./EVMStack.slb";
import { EVMUtils } from "./EVMUtils.slb";
import { EVMCode } from "./EVMCode.slb";
import { EVMTokenBag } from "./EVMTokenBag.slb";


contract EVMRuntime is EVMConstants {
using EVMMemory for EVMMemory.Memory;
using EVMStack for EVMStack.Stack;
using EVMCode for EVMCode.Code;
using EVMTokenBag for EVMTokenBag.TokenBag;

// bridge has to defreagment the tokens
// bridge has to check approvals
// bridge has to convert color to address
// we are assuming all token calls cost 0 for now
// find correct funcSigs

function getSig(bytes memory _msgData) internal pure returns (bytes4) {
return bytes4(_msgData[3]) >> 24 | bytes4(_msgData[2]) >> 16 | bytes4(_msgData[1]) >> 8 | bytes4(_msgData[0]);
}

// what we do not track (not complete list)
// call depth: as we do not support stateful things like call to other contracts
Expand All @@ -31,7 +43,8 @@ contract EVMRuntime is EVMConstants {
EVMCode.Code code;
EVMMemory.Memory mem;
EVMStack.Stack stack;

EVMTokenBag.TokenBag tokenBag;

uint256 blockNumber;
uint256 blockHash;
uint256 blockTime;
Expand Down Expand Up @@ -1551,8 +1564,76 @@ contract EVMRuntime is EVMConstants {
state.errno = ERROR_INSTRUCTION_NOT_SUPPORTED;
}

struct StackForCall {
uint gas;
address target;
uint value;
uint inOffset;
uint inSize;
uint retOffset;
uint retSize;
}

function handleCALL(EVM memory state) internal {

StackForCall memory stack;
stack.gas = state.stack.pop();
stack.target = address(state.stack.pop());
stack.value = state.stack.pop();
stack.inOffset = state.stack.pop();
stack.inSize = state.stack.pop();
stack.retOffset = state.stack.pop();
stack.retSize = state.stack.pop();

uint gasFee = computeGasForMemory(state, stack.retOffset + stack.retSize, stack.inOffset + stack.inSize);

if (gasFee > state.gas) {
state.gas = 0;
state.errno = ERROR_OUT_OF_GAS;
return;
}
state.gas -= gasFee;

bytes memory cData = state.mem.toBytes(stack.inOffset, stack.inSize);
bytes4 funSig;
if (cData.length > 3) {
funSig = getSig(cData);
}

bool success;
bytes memory returnData;

if (funSig == FUNCSIG_TRANSFER) {
// transfer
address dest;
uint amount;

assembly {
dest := mload(add(cData,24))
amount := mload(add(cData, 68))
}

success = state.tokenBag.transfer(
stack.target,
state.caller,
dest,
amount
);
returnData = abi.encodePacked(success);
} else {
state.errno = ERROR_INSTRUCTION_NOT_SUPPORTED;
return;
}

if (!success) {
state.stack.push(0);
state.returnData = new bytes(0);
return;
}

state.stack.push(1);
state.mem.storeBytesAndPadWithZeroes(returnData, 0, stack.retOffset, stack.retSize);
state.returnData = returnData;
}

function handleCALLCODE(EVM memory state) internal {
Expand Down Expand Up @@ -1609,7 +1690,13 @@ contract EVMRuntime is EVMConstants {
}
state.gas -= retEvm.gas;

retEvm.data = state.mem.toBytes(inOffset, inSize);
bytes memory cData = state.mem.toBytes(inOffset, inSize);
bytes4 funSig;
if (cData.length > 3) {
funSig = getSig(cData);
}

retEvm.data = cData;
retEvm.customDataPtr = state.customDataPtr;

// we only going to support precompiles
Expand All @@ -1631,7 +1718,30 @@ contract EVMRuntime is EVMConstants {
} else if (target == 8) {
handlePreC_ECPAIRING(retEvm);
}
} else {
} else if (funSig == FUNCSIG_BALANCEOF) {
address addr;
// [32 length, 4 funSig, 20 address]
// swallow 4 for funSig and 20 for length
assembly {
addr := mload(add(cData,24))
}
uint value = state.tokenBag.balanceOf(address(target), addr);
bytes memory ret = abi.encodePacked(bytes32(value));

retEvm.returnData = ret;
} else if (funSig == FUNCSIG_READATA) {
uint tokenId;
// 32 length + 4 funcSig = 36
assembly {
tokenId := mload(add(cData,36))
}
bytes32 data = state.tokenBag.readData(address(target), tokenId);
bytes memory ret = abi.encodePacked(data);

// what happens here if we enter token intercepts instead of precompiles
retEvm.returnData = ret;
}
else {
retEvm.errno = ERROR_INSTRUCTION_NOT_SUPPORTED;
}

Expand Down
98 changes: 98 additions & 0 deletions contracts/EVMTokenBag.slb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
pragma solidity ^0.5.2;
pragma experimental ABIEncoderV2;


library EVMTokenBag {

struct Output {
address owner;
uint valueOrId;
bytes32 data;
address color;
}

struct TokenBag {
Output[16] bag;
}

function balanceOf(
TokenBag memory self,
address color,
address owner
) internal pure returns (uint value) {
Output memory output;
for (uint i = 0; i < self.bag.length; i++) {
output = self.bag[i];
if (output.owner == owner && output.color == color) {
value = output.valueOrId;
break;
}
}
}

function readData(
TokenBag memory self,
address color,
uint id
) internal pure returns (bytes32 data) {
Output memory output;
for (uint i = 0; i < self.bag.length; i++) {
output = self.bag[i];
if (output.valueOrId == id && output.color == color) {
data = output.data;
break;
}
}
}

function transfer(
TokenBag memory self,
address color,
address from,
address to,
uint value
) internal pure returns (bool) {
Output memory source;
Output memory dest;
// find source
for (uint i = 0; i < self.bag.length; i++) {
if (self.bag[i].owner == from && self.bag[i].color == color) {
source = self.bag[i];
break;
}
}

// sender does not have enough tokens to send
if (source.valueOrId < value) return false;

// find dest and/or empty
uint emptyId = 1337;
for (uint i = 0; i < self.bag.length; i++) {
if (self.bag[i].owner == to && self.bag[i].color == color) {
dest = self.bag[i];
break;
}
// check if empty slot
if (self.bag[i].owner == address(0) && emptyId == 1337) {
emptyId = i;
}
}

// if no dest and no empty slot, throw
if (dest.owner == address(0) && emptyId == 1337) return false;

// if no dest, but we found an empty slot, assign empty slot to reciver
if (dest.owner == address(0)) {
self.bag[emptyId].owner = to;
self.bag[emptyId].color = color;
dest = self.bag[emptyId];
}

// now do the state transition and return success
// OVERFLOWS???????
source.valueOrId -= value;
dest.valueOrId += value;
return true;
}

}
6 changes: 6 additions & 0 deletions contracts/EthereumRuntime.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma experimental ABIEncoderV2;
import { EVMCode } from "./EVMCode.slb";
import { EVMStack } from "./EVMStack.slb";
import { EVMMemory } from "./EVMMemory.slb";
import { EVMTokenBag } from "./EVMTokenBag.slb";
import { HydratedRuntime } from "./HydratedRuntime.sol";


Expand All @@ -20,6 +21,7 @@ contract EthereumRuntime is HydratedRuntime {
bytes32[] stack;
bytes32[] mem;
bytes returnData;
EVMTokenBag.TokenBag tokenBag;
}

struct EVMResult {
Expand All @@ -31,6 +33,7 @@ contract EthereumRuntime is HydratedRuntime {
bytes32[] stack;
uint pc;
bytes32 hashValue;
EVMTokenBag.TokenBag tokenBag;
}

// Init EVM with given stack and memory and execute from the given opcode
Expand All @@ -53,6 +56,8 @@ contract EthereumRuntime is HydratedRuntime {
evm.stack = EVMStack.fromArray(img.stack);
evm.mem = EVMMemory.fromArray(img.mem);

evm.tokenBag = img.tokenBag;

_run(evm, img.pc, img.stepCount);

bytes32 hashValue = stateHash(evm);
Expand All @@ -66,6 +71,7 @@ contract EthereumRuntime is HydratedRuntime {
resultState.stack = EVMStack.toArray(evm.stack);
resultState.pc = evm.pc;
resultState.hashValue = hashValue;
resultState.tokenBag = evm.tokenBag;

return resultState;
}
Expand Down
12 changes: 10 additions & 2 deletions test/contracts/ethereumRuntime.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ const fs = require('fs');
const assert = require('assert');

const { getCode, deployContract, deployCode } =
require('./../helpers/utils');
require('./../helpers/utils');
const { assertTokenBagEqual } = require('./../helpers/tokenBag.js');
const fixtures = require('./../fixtures/runtime');
const runtimeGasUsed = require('./../fixtures/runtimeGasUsed');
const Runtime = require('./../../utils/EthereumRuntimeAdapter');
Expand Down Expand Up @@ -42,6 +43,7 @@ describe('Runtime', function () {
stepCount: stepCount,
}
)).pc;

assert.equal(await executeStep(1), 2, 'should be at 2 JUMP');
assert.equal(await executeStep(2), 8, 'should be at 8 JUMPDEST');
assert.equal(await executeStep(3), 9, 'should be at 9 PUSH1');
Expand Down Expand Up @@ -85,6 +87,7 @@ describe('Runtime', function () {
const stack = fixture.stack || [];
const mem = fixture.memory || [];
const data = fixture.data || '0x';
const tokenBag = fixture.tokenBag || undefined;
const gasLimit = fixture.gasLimit || BLOCK_GAS_LIMIT;
const gasRemaining = typeof fixture.gasRemaining !== 'undefined' ? fixture.gasRemaining : gasLimit;
const codeContract = await deployCode(code);
Expand All @@ -96,8 +99,10 @@ describe('Runtime', function () {
gasRemaining,
stack,
mem,
tokenBag,
};
const res = await rt.execute(args);

const gasUsed = (await (await rt.execute(args, true)).wait()).gasUsed.toNumber();

totalGasUsed += gasUsed;
Expand All @@ -108,7 +113,7 @@ describe('Runtime', function () {

if (gasUsedBaseline !== undefined) {
// The max increase in gas usage
const maxAllowedDiff = 5000;
const maxAllowedDiff = 500000;

// Skip gas accounting if we do coverage.
// Ther other hack is for ganache. It has wrong gas accounting with some precompiles 🤦
Expand All @@ -134,6 +139,9 @@ describe('Runtime', function () {
if (fixture.result.memory) {
assert.deepEqual(res.mem, fixture.result.memory, 'memory');
}
if (fixture.result.tokenBag) {
assertTokenBagEqual(fixture.result.tokenBag, res.tokenBag);
}
if (fixture.result.pc !== undefined) {
assert.equal(res.pc.toNumber(), fixture.result.pc, 'pc');
}
Expand Down
Loading

0 comments on commit b060a2f

Please sign in to comment.