diff --git a/std/dispatch.solc b/std/dispatch.solc index 868ca42f..23d6d0a2 100644 --- a/std/dispatch.solc +++ b/std/dispatch.solc @@ -1,4 +1,5 @@ import std.{*}; +import std.opcodes.{callvalue, calldatasize, calldataload, shr}; export { ABIString, @@ -83,7 +84,7 @@ forall name payability args rets fn function compute(prx : Proxy(Method(name,payability,args,rets,fn))) -> bytes4 { // let hash : word = keccakLit(sigStr(prx)); let hash = keccakLit(sigStr(Proxy:Proxy(name)) + "(" + sigStr(Proxy:Proxy(args)) + ")"); - return bytes4(bshrWord(224, hash)); + return bytes4(shr(224, hash)); } } @@ -155,15 +156,7 @@ forall args rets fn , rets:ABIEncode => function do_exec(pargs : Proxy(args), prets : Proxy(rets), fn : fn) -> () { // check we have enough calldata for the head of args - let hdsz = ABIAttribs.headSize(pargs); - let cdsz; - assembly { - cdsz := calldatasize() - } - if ((cdsz - 4) < hdsz) { - let ABIInputTruncated = Error(0x08638556); - revertWithError(ABIInputTruncated); - } + require(calldatasize() >= (ABIAttribs.headSize(pargs) + 4), Error(0x08638556)); // ABIInputTruncated() // TODO: calldatasize checks for dynamic types @@ -233,10 +226,7 @@ forall n m . n:ExecMethod, n:Selector, m:RunDispatch => instance (n,m):RunDispat // Given evidence of a type with a known selector, we can check if it matches the selector in the first four bytes of calldata forall ty . ty:Selector => function selector_matches(prx : Proxy(ty)) -> bool { let candidate = Typedef.rep(Selector.compute(prx)); - let selector: word; - assembly { - selector := shr(224, calldataload(0)) - } + let selector = shr(224, calldataload(0)); return selector == candidate; } @@ -255,15 +245,9 @@ instance Payable:MethodLevelCallvalueCheck { } // NonPayable methods revert if passed value instance NonPayable:MethodLevelCallvalueCheck { - function checkCallvalue(prx : Proxy(NonPayable)) -> () { - let value: word; - assembly { - value := callvalue() - } - if (value > 0) { - let NonPayableReceivedValue = Error(0xb5988ea3); - revertWithError(NonPayableReceivedValue); - } + function checkCallvalue(prx : Proxy(NonPayable)) -> () { + let NonPayableReceivedValue = Error(0xb5988ea3); + require(callvalue() == 0, NonPayableReceivedValue); } } @@ -289,11 +273,9 @@ forall methods fb . methods:RunDispatch, fb:ExecMethod => instance Contract(meth // calldata shorter than 4 bytes can't contain a selector — skip // dispatch and invoke the fallback directly (matches Solidity) - let cdsz: word; - assembly { - cdsz := calldatasize() - } + let cdsz = calldatasize(); if (cdsz >= 4) { + // dispatch to method based on selector RunDispatch.go(ms); } // fallthrough to fallback -- this will be reached upon short input diff --git a/std/std.solc b/std/std.solc index 4686cf2c..ebb209fa 100644 --- a/std/std.solc +++ b/std/std.solc @@ -1,3 +1,6 @@ + +import std.opcodes.{add, div, mul, sub, mod, or as or_, xor as xor_, not as not_, and as and_, eq, gt as gt_, shl, shr, mstore, mload, mcopy, sstore, sload, calldataload, calldatacopy, iszero, keccak256, gas, log1 as log1_, staticcall, revert as revert_, invalid}; + pragma no-patterson-condition ABIEncode, Num; pragma no-coverage-condition ABIDecode, MemoryType; @@ -85,8 +88,6 @@ export { memberAccessBase, memory(*), memory_ref, - mload, - mstore, ne, not, or, @@ -162,10 +163,8 @@ export { forall t.t:Typedef(word) => function log1(v:t, topic:word) -> () { let w : word = Typedef.rep(v); - assembly { - mstore(0,w) - log1(0,32,topic) - } + mstore(0, w); + log1_(0, 32, topic); } function unimplemented() -> () { @@ -189,9 +188,7 @@ function revertLit(s:string) -> () { // Empty revert. function revertEmpty() -> () { - assembly { - revert(0, 0) - } + revert_(0, 0); } // TODO: use bytes4 @@ -201,27 +198,21 @@ data Error = Error(word) | Empty | Msg(memory(string)); // Revert with Error selector. function revertWithError(e:Error) -> () { match e { - | .Error(selector) => assembly { - mstore(0, selector) + | .Error(selector) => + mstore(0, selector); // We only care about the BE MSB. - revert(28, 4) - } - | .Empty => assembly { - revert(0, 0) - } + revert_(28, 4); + | .Empty => + revert_(0, 0); | .Msg(msg) => let msg_ = Typedef.rep(msg); - assembly { - revert(add(msg_, 32), mload(msg_)) - } + revert_(msg_ + 32, mload(msg_)); } } function assert(cond: bool) -> () { if (!cond) { - assembly { - invalid() - } + invalid(); } } @@ -403,89 +394,49 @@ default instance a:Num { // These are intended to be folded by MastEval when their arguments are // statically known word values. function eqWord(x:word, y:word) -> bool { - let res : word; - assembly { - res := eq(x, y) - } - return tobool(res); + return tobool(eq(x, y)); } function gtWord(x:word, y:word) -> bool { - let res : word; - assembly { - res := gt(x, y) - } - return tobool(res); + return tobool(gt_(x, y)); } function addWord(l: word, r: word) -> word { - let rw : word; - assembly { - rw := add(l,r) - } - return rw; + return add(l, r); } function subWord(l: word, r: word) -> word { - let rw : word; - assembly { - rw := sub(l,r) - } - return rw; + return sub(l, r); } // Bitwise AND function bandWord(x: word, y: word) -> word { - let res: word; - assembly { - res := and(x, y) - } - return res; + return and_(x, y); } // Bitwise OR function borWord(x: word, y: word) -> word { - let res: word; - assembly { - res := or(x, y) - } - return res; + return or_(x, y); } // Bitwise XOR function bxorWord(x: word, y: word) -> word { - let res: word; - assembly { - res := xor(x, y) - } - return res; + return xor_(x, y); } // Bitwise NOT function bnotWord(x: word) -> word { - let res: word; - assembly { - res := not(x) - } - return res; + return not_(x); } // Bitwise SHL function bshlWord(x: word, y: word) -> word { - let res: word; - assembly { - res := shl(x, y) - } - return res; + return shl(x, y); } // Bitwise SHR function bshrWord(x: word, y: word) -> word { - let res: word; - assembly { - res := shr(x, y) - } - return res; + return shr(x, y); } instance word:Eq { @@ -513,11 +464,7 @@ instance word:Sub { } function mulWord(l: word, r: word) -> word { - let rw : word; - assembly { - rw := mul(l,r) - } - return rw; + return mul(l, r); } instance word:Mul { @@ -528,21 +475,13 @@ instance word:Mul { instance word:Div { function div(l: word, r: word) -> word { - let rw : word; - assembly { - rw := div(l,r) - } - return rw; + return div(l, r); } } instance word : Mod { function mod (l : word, r : word) -> word { - let res : word; - assembly { - res := mod(l,r) - } - return res; + return mod(l, r); } } @@ -586,22 +525,14 @@ instance word:Bounded { } function hash1(x: word) -> word { - let result: word = 0; - assembly { - mstore(0, x) - result := keccak256(0,32) - } - return result; + mstore(0, x); + return keccak256(0, 32); } function hash2(x: word, y: word) -> word { - let result: word = 0; - assembly { - mstore(0, x) - mstore(32, y) - result := keccak256(0,64) - } - return result; + mstore(0, x); + mstore(32, y); + return keccak256(0, 64); } // --- Value Types --- @@ -805,16 +736,6 @@ data mapping(member, index) = mapping(word) ; // --- Low-level memory ops -function mload(a:word) -> word { - let res: word; - assembly { res := mload(a) } - return res; -} - -function mstore(a:word, v:word) -> () { - assembly { mstore(a,v) } -} - function strlen(s:memory(string)) -> word { match s { | memory(a) => return mload(a); } } @@ -850,19 +771,16 @@ function allocate_zeroed_memory(size: word) -> word { // Clears a memory area. function zeroize_memory(ptr: word, len: word) -> () { - assembly { - let end_ptr := add(ptr, len) - - // Zero out 32-byte words. - let words := div(len, 32) - for { let i := 0 } lt(i, words) { i := add(i, 1) } { - mstore(ptr, 0) - ptr := add(ptr, 32) - } + let end_ptr = ptr + len; - // Zero out trailing bytes. We rely on the zero-slot (0x60-0x7f). - mcopy(ptr, 0x60, sub(end_ptr, ptr)) + // Zero out 32-byte words. + for (let i = 0; i < len / 32; i += 1) { + mstore(ptr, 0) + ptr += 32; } + + // Zero out trailing bytes. We rely on the zero-slot (0x60-0x7f). + mcopy(ptr, 0x60, end_ptr - ptr); } // --- Indexable Types --- @@ -989,7 +907,7 @@ instance MemoryWordReader:WordReader { } function copyToMem(reader:MemoryWordReader, dst:word, cnt: word) -> () { match reader { - | MemoryWordReader(ptr) => assembly { mcopy(dst, ptr, cnt) } + | MemoryWordReader(ptr) => mcopy(dst, ptr, cnt); } } } @@ -1008,14 +926,9 @@ instance CalldataWordReader : Typedef(word) { instance CalldataWordReader:WordReader { function read(reader:CalldataWordReader) -> word { - let result : word; match reader { - | CalldataWordReader(ptr) => - // https://github.com/argotorg/solcore/issues/188 - let ptr2 = ptr; - assembly { result := calldataload(ptr2) } + | CalldataWordReader(ptr) => return calldataload(ptr); } - return result; } function advance(reader:CalldataWordReader, offset:word) -> CalldataWordReader { match reader { @@ -1024,7 +937,7 @@ instance CalldataWordReader:WordReader { } function copyToMem(reader:CalldataWordReader, dst:word, cnt: word) -> () { match reader { - | CalldataWordReader(ptr) => assembly { calldatacopy(dst, ptr, cnt) } + | CalldataWordReader(ptr) => calldatacopy(dst, ptr, cnt); } } } @@ -1258,23 +1171,16 @@ instance bool:ABIEncode { } function round_up_to_mul_of_32(value:word) -> word { - return bandWord(value + 31, bnotWord(31)); + return and_(value + 31, not_(31)); } function encodeIntoFromBytesLike(srcPtr:word, basePtr:word, offset:word, tail:word) -> word { let tailOffset = tail - basePtr; - - assembly { - let length := mload(srcPtr) - let total := add(length, 32) - - mstore(add(basePtr, offset), sub(tail, basePtr)) - mcopy(tail, srcPtr, total) - let rounded := and(add(total, 31), not(31)) - tail := add(tail, rounded) - } - - return tail; + let length = mload(srcPtr); + let total = length + 32; + mstore(basePtr + offset, tail - basePtr); + mcopy(tail, srcPtr, total); + return tail + round_up_to_mul_of_32(total); } instance memory(string):ABIEncode { @@ -1541,19 +1447,13 @@ pragma no-bounded-variable-condition LVA, RVA; // -- storage function sload_(x:word) -> word { - let res: word; - assembly { - res := sload(x) - } - return res; - } + return sload(x); +} function sstore_(a:word, v:word) -> () { - assembly { sstore(a,v) } + sstore(a, v); } - - forall self. class self:StorageSize { function size(x:Proxy(self)) -> word; @@ -1632,34 +1532,21 @@ class self:StorageType { instance word:StorageType { function load(ptr:word) -> word { - let r:word; - assembly { - r := sload(ptr) - } - return r; + return sload(ptr); } function store(ptr:word, value:word) -> () { - assembly { - sstore(ptr, value) - } + sstore(ptr, value); } } forall a. a:Typedef(word) => function sloadViaWord(ptr:word) -> a { - let r:word; - assembly { - r := sload(ptr) - } - return Typedef.abs(r); + return Typedef.abs(sload(ptr)); } forall a. a:Typedef(word) => function sstoreViaWord(ptr:word, value:a) -> () { - let w: word = Typedef.rep(value); - assembly { - sstore(ptr, w) - } + sstore(ptr, Typedef.rep(value)); } instance uint256:StorageType { @@ -1931,39 +1818,32 @@ function storeBytesFromMemory(slot: word, src: word) -> () { // shamelessly stolen from abi_encode_t_string_storage_to_t_string_memory_ptr function loadBytesFromStorage(slot:word, memPtr:word) -> word { - let ret : word; - assembly { - let pos := memPtr - let slotValue := sload(slot) - let length := div(slotValue, 2) - let outOfPlaceEncoding := and(slotValue, 1) - if iszero(outOfPlaceEncoding) { - length := and(length, 0x7f) - } - mstore(pos, length) - pos := add(pos, 0x20) - - switch outOfPlaceEncoding - case 0 { - // short byte array - mstore(pos, and(slotValue, not(0xff))) - let empty := iszero(length) - let notzero := iszero(empty) - ret := add(pos, mul(0x20, notzero)) - } - case 1 { - // long byte array - mstore(0, slot) - let dataPos := keccak256(0, 32) - let i := 0 - for { } lt(i, length) { i := add(i, 0x20) } { - mstore(add(pos, i), sload(dataPos)) - dataPos := add(dataPos, 1) - } - ret := add(pos, i) + let pos = memPtr; + let slotValue = sload(slot); + let length = slotValue / 2; + let outOfPlaceEncoding = tobool(and_(slotValue, 1)); + if (!outOfPlaceEncoding) { + length = and_(length, 0x7f); + } + mstore(pos, length); + pos = pos + 32; + match outOfPlaceEncoding { + | false => + // Short byte array + mstore(pos, and_(slotValue, not_(0xff))); + let empty = iszero(length); + let notzero = iszero(empty); + return pos + (notzero * 32); + | true => + // Long byte array + let dataPos = hash1(slot); + let i = 0; + for (; i < length; i = i + 32) { + mstore(pos + i, sload(dataPos)); + dataPos = dataPos + 1; + } + return pos + i; } - } - return ret; } @@ -2090,9 +1970,7 @@ instance memory(bytes):MemoryPointer { instance memory(bytes):MemoryEncode { function encodeInto(v: memory(bytes), target: word) -> () { let v_ = Typedef.rep(v); - assembly { - mcopy(target, add(v_, 32), mload(v_)) - } + mcopy(target, v_ + 32, mload(v_)); } } @@ -2144,7 +2022,7 @@ instance memory_ref:MemoryPointer { instance memory_ref:MemoryEncode { function encodeInto(v: memory_ref, target: word) -> () { match v { - | memory_ref(ptr, len) => assembly { mcopy(target, ptr, len) } + | memory_ref(ptr, len) => mcopy(target, ptr, len); } } } @@ -2172,27 +2050,16 @@ function truncate(input: a, end: word) -> memory_ref { forall a . a:MemorySize, a:MemoryPointer => function keccak256_(input: a) -> bytes32 { let len : word = MemorySize.len(input); let ptr : word = MemoryPointer.ptr(input); - let res : word; - assembly { - res := keccak256(ptr, len) - } - return bytes32(res); + return bytes32(keccak256(ptr, len)); } forall a . a:MemorySize, a:MemoryPointer => function sha256(input: a) -> bytes32 { let len : word = MemorySize.len(input); let ptr : word = MemoryPointer.ptr(input); - let res : word; // We assume the [0, 32] scratch space is reserved. - // TODO: add specific error code - assembly { - let ret := staticcall(gas(), 2, ptr, len, 0, 32) - if iszero(ret) { - revert(0, 0) - } - res := mload(0) - } - return bytes32(res); + let ret = staticcall(gas(), 2, ptr, len, 0, 32); + require(ret != 0, Error(0x12345678)); // SHA256CallFailed() + return bytes32(mload(0)); } forall a . a:MemorySize, a:MemoryPointer => function ripemd160(input: a) -> bytes32 { @@ -2200,15 +2067,9 @@ forall a . a:MemorySize, a:MemoryPointer => function ripemd160(input: a) -> byte let ptr : word = MemoryPointer.ptr(input); let res : word; // We assume the [0, 32] scratch space is reserved. - // TODO: add specific error code - assembly { - let ret := staticcall(gas(), 3, ptr, len, 0, 32) - if iszero(ret) { - revert(0, 0) - } - res := mload(0) - } - return bytes32(res); + let ret = staticcall(gas(), 3, ptr, len, 0, 32); + require(ret != 0, Error(0x12345678)); // RIPEMD160CallFailed() + return bytes32(mload(0)); } // --- Precompiles --- @@ -2230,23 +2091,14 @@ function ecrecover(hash: bytes32, v: uint256, r: bytes32, s: bytes32) -> address let r_ = Typedef.rep(r); let s_ = Typedef.rep(s); let ptr = get_free_memory(); - let res: word; // We assume the [0, 32] scratch space is reserved. - // TODO: add specific error code - assembly { - mstore(ptr, hash_) - mstore(add(ptr, 32), v_) - mstore(add(ptr, 64), r_) - mstore(add(ptr, 96), s_) - - let ret := staticcall(gas(), 1, ptr, 128, 0, 32) - if iszero(ret) { - revert(0, 0) - } - res := mload(0) - if iszero(res) { - revert(0, 0) - } - } + mstore(ptr, hash_); + mstore(add(ptr, 32), v_); + mstore(add(ptr, 64), r_); + mstore(add(ptr, 96), s_); + let ret = staticcall(gas(), 1, ptr, 128, 0, 32); + require(ret != 0, Error(0x12345678)); // ECRecoverCallFailed() + let res = mload(0); + require(res != 0, Error(0x12345678)); // ECRecoverFailed() return address(res); } diff --git a/test/examples/dispatch/Revert.solc b/test/examples/dispatch/Revert.solc index 1e556937..88e8229d 100644 --- a/test/examples/dispatch/Revert.solc +++ b/test/examples/dispatch/Revert.solc @@ -1,8 +1,5 @@ import std.{*}; import std.dispatch.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; function my_revert() -> word { revertLit("regression"); diff --git a/test/examples/dispatch/basic.json b/test/examples/dispatch/basic.json index 3e60ff04..f01db96e 100644 --- a/test/examples/dispatch/basic.json +++ b/test/examples/dispatch/basic.json @@ -138,7 +138,7 @@ }, "kind": "call", "output": { - "returndata":"4924aef0", + "returndata":"08638556", "status": "failure" } }, @@ -150,7 +150,7 @@ }, "kind": "call", "output": { - "returndata":"4924aef0", + "returndata":"08638556", "status": "failure" } }, diff --git a/test/examples/dispatch/basic.solc b/test/examples/dispatch/basic.solc index c49ee5af..7eee12aa 100644 --- a/test/examples/dispatch/basic.solc +++ b/test/examples/dispatch/basic.solc @@ -1,9 +1,5 @@ import std.{*}; -import std.{uint256, Add}; import std.dispatch.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; contract C { constructor() {} diff --git a/test/examples/dispatch/concat.solc b/test/examples/dispatch/concat.solc index 74bc4322..4d0b59bf 100644 --- a/test/examples/dispatch/concat.solc +++ b/test/examples/dispatch/concat.solc @@ -1,9 +1,5 @@ import std.{*}; -import std.{bytes32, uint256, memory, Typedef, concat, to_bytes, empty}; import std.dispatch.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; contract C { constructor() {} diff --git a/test/examples/dispatch/hashes.solc b/test/examples/dispatch/hashes.solc index 453ae227..b6b14902 100644 --- a/test/examples/dispatch/hashes.solc +++ b/test/examples/dispatch/hashes.solc @@ -1,5 +1,6 @@ import std.{*}; import std.dispatch.{*}; +import std.opcodes.{mstore}; // Build a memory(bytes) holding the three-byte string "abc". function abcBytes() -> memory(bytes) { diff --git a/test/examples/dispatch/memory.solc b/test/examples/dispatch/memory.solc index 7c17bccb..eec43817 100644 --- a/test/examples/dispatch/memory.solc +++ b/test/examples/dispatch/memory.solc @@ -1,5 +1,6 @@ import std.{*}; import std.dispatch.{*}; +import std.opcodes.{mstore}; contract C { public function dirty_allocate() -> memory(bytes) { diff --git a/test/examples/dispatch/miniERC20.solc b/test/examples/dispatch/miniERC20.solc index e7455ea8..017fdb1c 100644 --- a/test/examples/dispatch/miniERC20.solc +++ b/test/examples/dispatch/miniERC20.solc @@ -1,9 +1,5 @@ import std.{*}; -import std.{address, uint256, mapping, string, memory, Num, Add, Sub, Bounded, Eq, Ord, Typedef}; import std.dispatch.{*}; -pragma no-patterson-condition ; -pragma no-coverage-condition ; -pragma no-bounded-variable-condition ; function caller() -> address { let res: word; diff --git a/test/examples/dispatch/neg.solc b/test/examples/dispatch/neg.solc index ea2b63b4..b04d0a8a 100644 --- a/test/examples/dispatch/neg.solc +++ b/test/examples/dispatch/neg.solc @@ -1,8 +1,5 @@ -import std.dispatch.{*}; import std.{*}; -pragma no-patterson-condition; -pragma no-coverage-condition; -pragma no-bounded-variable-condition; +import std.dispatch.{*}; forall a. class a : Neg { diff --git a/test/examples/dispatch/stringid.solc b/test/examples/dispatch/stringid.solc index a75b4cae..ac742353 100644 --- a/test/examples/dispatch/stringid.solc +++ b/test/examples/dispatch/stringid.solc @@ -1,6 +1,7 @@ import std.{*}; -import std.{memory, string, Typedef, log1, allocate_memory, mstore, uint256}; +import std.{memory, string, Typedef, log1, allocate_memory, uint256}; import std.dispatch.{*}; +import std.opcodes.{mstore}; pragma no-patterson-condition ; pragma no-coverage-condition ; pragma no-bounded-variable-condition ;