From 750a72659c8a1549d29a79119a9bd1f11e1604e7 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Sun, 2 Nov 2025 09:31:19 +0100 Subject: [PATCH 01/11] Attempt to use Storable in LVA, RVA --- std/std.solc | 75 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/std/std.solc b/std/std.solc index 704f8cfb..5d4f44f5 100644 --- a/std/std.solc +++ b/std/std.solc @@ -1082,7 +1082,7 @@ forall baseType baseType_decoded reader . ABIDecoder(baseType, CalldataWordReade pragma no-patterson-condition RVA; pragma no-coverage-condition MemberAccessProxy, LVA, RVA, StructField; - +pragma no-bounded-variable-condition LVA, RVA; // -- storage function sload_(x:word) -> word { @@ -1098,11 +1098,20 @@ function sstore_(a:word, v:word) { } + forall self. class self:StorageSize { function size(x:Proxy(self)) -> word; } + +forall self. +default instance self:StorageSize { + function size(x:Proxy(self)) -> word { + return 1; + } +} + instance ():StorageSize { function size(x:Proxy(())) -> word { return 0; @@ -1133,7 +1142,19 @@ instance address:StorageSize { } } -forall a b . a:StorageSize, b:StorageSize => instance (a,b):StorageSize { +instance string:StorageSize { + function size(x:Proxy(string)) -> word { + return 1; + } +} + +instance memory(string):StorageSize { + function size(x:Proxy(memory(string))) -> word { + return 1; + } +} + +forall a b. a:StorageSize, b:StorageSize => instance (a,b):StorageSize { function size(x:Proxy((a,b))) -> word { let a_sz:word = StorageSize.size(Proxy:Proxy(a)); let b_sz:word = StorageSize.size(Proxy:Proxy(b)); @@ -1206,10 +1227,10 @@ class self:StructField(fieldType, offsetType) {} data StructField(structType, fieldSelector) = StructField(structType); -data MemberAccessProxy(a, field, offset) = MemberAccessProxy(a, field); +data MemberAccessProxy(a, field, fieldtype, storageType, offset) = MemberAccessProxy(a, field); -forall a field offset . -function memberAccessBase(x:MemberAccessProxy(a, field, offset)) -> a { +forall a field fieldType storageType offset . +function memberAccessBase(x:MemberAccessProxy(a, field, fieldType, storageType, offset)) -> a { match x { | MemberAccessProxy(y,z) => return y; } @@ -1219,7 +1240,8 @@ function memberAccessBase(x:MemberAccessProxy(a, field, offset)) -> a { // ------------------------------------------------------------------ // Contract field access // ------------------------------------------------------------------ - +// FIXME: strings / Storable +/* forall cxt fieldSelector fieldType offsetType . StructField(ContractStorage(cxt), fieldSelector):StructField(fieldType, offsetType) , offsetType:StorageSize @@ -1229,9 +1251,20 @@ forall cxt fieldSelector fieldType offsetType return storage(offset); } } - +*/ +forall cxt fieldSelector fieldType offsetType storageType +. StructField(ContractStorage(cxt), fieldSelector) : StructField (fieldType, offsetType) +, offsetType : StorageSize +, fieldType : Storable(storageType) +=> instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, storageType, offsetType) : LVA (storage(storageType)) { + function acc (x : MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, storageType, offsetType)) -> storage(storageType) { + let offset : word = StorageSize.size(Proxy : Proxy(offsetType)) ; + return storage(offset):storage(storageType); + } +} +/* forall cxt fieldSelector fieldType offsetType - . StructField(ContractStorage(cxt), fieldSelector):StructField(fieldType, offsetType) + . StructField(ContractStorage(cxt), fieldSelector):StructField(fieldType, storageType, offsetType) , fieldType:StorageType , offsetType:StorageSize => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType):RVA(fieldType) { @@ -1240,13 +1273,25 @@ forall cxt fieldSelector fieldType offsetType return StorageType.sload(offset):fieldType; } } +*/ +forall cxt fieldSelector fieldType offsetType storageType + . StructField(ContractStorage(cxt), fieldSelector):StructField(fieldType, storageType, offsetType) + , fieldType:Storable(storageType) +// , fieldType:StorageType + , offsetType:StorageSize + => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, storageType, offsetType):RVA(fieldType) { + function acc(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, storageType, offsetType)) -> fieldType { + let offset:word = StorageSize.size(Proxy:Proxy(offsetType)); + return Storable.sload(storage(offset):storage(storageType)):fieldType; + } +} -forall structType fieldSelector fieldType offsetType - . StructField(structType, fieldSelector):StructField(fieldType, offsetType) +forall structType fieldSelector fieldType storageType offsetType + . StructField(structType, fieldSelector):StructField(fieldType, storageType, offsetType) , offsetType:StorageSize - => instance MemberAccessProxy(storage(structType), fieldSelector, offsetType):LVA(storage(fieldType)) { - function acc(x:MemberAccessProxy(storage(structType), fieldSelector, offsetType)) -> storage(fieldType) { + => instance MemberAccessProxy(storage(structType), fieldSelector, fieldType, storageType, offsetType):LVA(storage(fieldType)) { + function acc(x:MemberAccessProxy(storage(structType), fieldSelector, fieldType, storageType, offsetType)) -> storage(fieldType) { let ptr:word = Typedef.rep(memberAccessBase(x)); let size:word = StorageSize.size(Proxy:Proxy(offsetType)); assembly { @@ -1257,12 +1302,12 @@ forall structType fieldSelector fieldType offsetType } -forall structType fieldSelector fieldType offsetType +forall structType fieldSelector fieldType storageType offsetType . StructField(structType, fieldSelector):StructField(fieldType, offsetType) , offsetType:StorageSize , fieldType:StorageType - => instance MemberAccessProxy(storage(structType), fieldSelector, offsetType):RVA(fieldType) { - function acc(x:MemberAccessProxy(storage(structType), fieldSelector, offsetType)) -> fieldType { + => instance MemberAccessProxy(storage(structType), fieldSelector, fieldType, storageType, offsetType):RVA(fieldType) { + function acc(x:MemberAccessProxy(storage(structType), fieldSelector, fieldType, storageType, offsetType)) -> fieldType { let ptr:word = Typedef.rep(memberAccessBase(x)); let size:word = StorageSize.size(Proxy:Proxy(offsetType)); assembly { From 8c7ac4dceb753828aaafa680758ffc0aab3cb9db Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Mon, 3 Nov 2025 17:06:41 +0100 Subject: [PATCH 02/11] std: remove storageType froom MemberAccessProxy --- std/std.solc | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/std/std.solc b/std/std.solc index 5d4f44f5..bbc87bf3 100644 --- a/std/std.solc +++ b/std/std.solc @@ -1227,10 +1227,10 @@ class self:StructField(fieldType, offsetType) {} data StructField(structType, fieldSelector) = StructField(structType); -data MemberAccessProxy(a, field, fieldtype, storageType, offset) = MemberAccessProxy(a, field); +data MemberAccessProxy(a, field, fieldtype, offset) = MemberAccessProxy(a, field); forall a field fieldType storageType offset . -function memberAccessBase(x:MemberAccessProxy(a, field, fieldType, storageType, offset)) -> a { +function memberAccessBase(x:MemberAccessProxy(a, field, fieldType, offset)) -> a { match x { | MemberAccessProxy(y,z) => return y; } @@ -1256,8 +1256,8 @@ forall cxt fieldSelector fieldType offsetType storageType . StructField(ContractStorage(cxt), fieldSelector) : StructField (fieldType, offsetType) , offsetType : StorageSize , fieldType : Storable(storageType) -=> instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, storageType, offsetType) : LVA (storage(storageType)) { - function acc (x : MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, storageType, offsetType)) -> storage(storageType) { +=> instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, offsetType) : LVA (storage(storageType)) { + function acc (x : MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, offsetType)) -> storage(storageType) { let offset : word = StorageSize.size(Proxy : Proxy(offsetType)) ; return storage(offset):storage(storageType); } @@ -1276,22 +1276,22 @@ forall cxt fieldSelector fieldType offsetType */ forall cxt fieldSelector fieldType offsetType storageType - . StructField(ContractStorage(cxt), fieldSelector):StructField(fieldType, storageType, offsetType) + . StructField(ContractStorage(cxt), fieldSelector):StructField(fieldType, offsetType) , fieldType:Storable(storageType) // , fieldType:StorageType , offsetType:StorageSize - => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, storageType, offsetType):RVA(fieldType) { - function acc(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, storageType, offsetType)) -> fieldType { + => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, offsetType):RVA(fieldType) { + function acc(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, offsetType)) -> fieldType { let offset:word = StorageSize.size(Proxy:Proxy(offsetType)); return Storable.sload(storage(offset):storage(storageType)):fieldType; } } forall structType fieldSelector fieldType storageType offsetType - . StructField(structType, fieldSelector):StructField(fieldType, storageType, offsetType) + . StructField(structType, fieldSelector):StructField(fieldType, offsetType) , offsetType:StorageSize - => instance MemberAccessProxy(storage(structType), fieldSelector, fieldType, storageType, offsetType):LVA(storage(fieldType)) { - function acc(x:MemberAccessProxy(storage(structType), fieldSelector, fieldType, storageType, offsetType)) -> storage(fieldType) { + => instance MemberAccessProxy(storage(structType), fieldSelector, fieldType, offsetType):LVA(storage(fieldType)) { + function acc(x:MemberAccessProxy(storage(structType), fieldSelector, fieldType, offsetType)) -> storage(fieldType) { let ptr:word = Typedef.rep(memberAccessBase(x)); let size:word = StorageSize.size(Proxy:Proxy(offsetType)); assembly { @@ -1306,8 +1306,8 @@ forall structType fieldSelector fieldType storageType offsetType . StructField(structType, fieldSelector):StructField(fieldType, offsetType) , offsetType:StorageSize , fieldType:StorageType - => instance MemberAccessProxy(storage(structType), fieldSelector, fieldType, storageType, offsetType):RVA(fieldType) { - function acc(x:MemberAccessProxy(storage(structType), fieldSelector, fieldType, storageType, offsetType)) -> fieldType { + => instance MemberAccessProxy(storage(structType), fieldSelector, fieldType, offsetType):RVA(fieldType) { + function acc(x:MemberAccessProxy(storage(structType), fieldSelector, fieldType, offsetType)) -> fieldType { let ptr:word = Typedef.rep(memberAccessBase(x)); let size:word = StorageSize.size(Proxy:Proxy(offsetType)); assembly { From 5f4c012b56f901d937bfcc56ab5d18d77c1f7f98 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Mon, 3 Nov 2025 19:39:17 +0100 Subject: [PATCH 03/11] TcSimplify: log substitutions from default instances --- src/Solcore/Frontend/TypeInference/TcSimplify.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Solcore/Frontend/TypeInference/TcSimplify.hs b/src/Solcore/Frontend/TypeInference/TcSimplify.hs index 7f388a0e..e5c23993 100644 --- a/src/Solcore/Frontend/TypeInference/TcSimplify.hs +++ b/src/Solcore/Frontend/TypeInference/TcSimplify.hs @@ -159,7 +159,7 @@ toHnf depth p@(InCls c _ _) info [">>>> No default instance found for:", pretty p] pure [p] Just (_, s) -> do - info [">>>> Default instance for:", pretty p, "found! (Solved)"] + info [">>>> Default instance for:", pretty p, " found! (Solved), \n>>> Subst: ", pretty s] -- default instances should not have any additional contraints. _ <- extSubst s pure [] From 7c5f7959fe5979a00770c4d72d35a9459cf332f5 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Mon, 3 Nov 2025 19:40:18 +0100 Subject: [PATCH 04/11] wip string contract fields --- std/dispatch.solc | 11 +++ std/std.solc | 70 +++++++++++++++- test/examples/spec/129miniERC20.solc | 115 +++++++++++++++++++++++++++ 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 test/examples/spec/129miniERC20.solc diff --git a/std/dispatch.solc b/std/dispatch.solc index 2d9e480e..3eb0f6bc 100644 --- a/std/dispatch.solc +++ b/std/dispatch.solc @@ -32,6 +32,17 @@ instance uint256:ABIString { } } +instance address:ABIString { + function append(head : word, tail : word, prx : Proxy(address)) -> word { + let size : word = 7; + assembly { + mstore(head, add(mload(head), size)) + mstore(tail, 0x6164647265737300000000000000000000000000000000000000000000000000) + } + return Add.add(tail, size); + } +} + instance ():ABIString { function append(head : word, tail : word, prx : Proxy(())) -> word { return(tail); diff --git a/std/std.solc b/std/std.solc index bbc87bf3..9942fc25 100644 --- a/std/std.solc +++ b/std/std.solc @@ -737,6 +737,12 @@ forall self . class self:ABIAttribs { function isStatic(ty:Proxy(self)) -> bool; } +forall t. +default instance t:ABIAttribs { + function headSize(ty : Proxy(t)) -> word { return 32; } + function isStatic(ty : Proxy(t)) -> bool { return true; } +} + instance ():ABIAttribs { function headSize(ty : Proxy(())) -> word { return 0; } function isStatic(ty : Proxy(())) -> bool { return true; } @@ -745,6 +751,10 @@ instance uint256:ABIAttribs { function headSize(ty : Proxy(uint256)) -> word { return 32; } function isStatic(ty : Proxy(uint256)) -> bool { return true; } } +instance address:ABIAttribs { + function headSize(ty : Proxy(address)) -> word { return 32; } + function isStatic(ty : Proxy(address)) -> bool { return true; } +} forall t . instance DynArray(t):ABIAttribs { function headSize(ty : Proxy(DynArray(t))) -> word { return 32; } function isStatic(ty : Proxy(DynArray(t))) -> bool { return false; } @@ -837,6 +847,16 @@ instance uint256:ABIEncode { } } +instance bool:ABIEncode { + + function encodeInto(x:bool, basePtr:word, offset:word, tail:word) -> word { + + let repx : word = frombool(x); + assembly { mstore(add(basePtr, offset), repx) } + return tail; + } +} + function round_up_to_mul_of_32(value:word) -> word { let result : word; assembly { result := and(add(value, 31), not(31)) } @@ -951,6 +971,13 @@ forall reader . reader:WordReader => instance ABIDecoder(uint256, reader):ABIDec } } +// ABI Decoding for address +forall reader . reader:WordReader => instance ABIDecoder(address, reader):ABIDecode(address) { + function decode(ptr:ABIDecoder(address, reader), currentHeadOffset:word) -> address { + return Typedef.abs(WordReader.read(WordReader.advance(ptr, currentHeadOffset))) : address; + } +} + forall reader . reader:WordReader => instance ABIDecoder((), reader):ABIDecode(()) { function decode(ptr:ABIDecoder((), reader), currentHeadOffset:word) -> () { return (); @@ -1380,7 +1407,7 @@ instance storage(b):Assign(a) { Storable.store(l, r); } } - +/* forall a. a:StorageType => default instance a:Storable(a) { function store(l:storage(a), r:a) -> () { @@ -1390,6 +1417,47 @@ default instance a:Storable(a) { return StorageType.sload(Typedef.rep(l)); } } +*/ + + instance word:Storable(word) { + function store(l:storage(word), r:word) -> () { + StorageType.store(Typedef.rep(l), r); + } + function sload(l:storage(word)) -> word { + return StorageType.sload(Typedef.rep(l)); + } +} + + instance uint256:Storable(uint256) { + function store(l:storage(uint256), r:uint256) -> () { + StorageType.store(Typedef.rep(l), r); + } + function sload(l:storage(uint256)) -> uint256 { + return StorageType.sload(Typedef.rep(l)); + } +} + + instance address:Storable(address) { + function store(l:storage(address), r:address) -> () { + StorageType.store(Typedef.rep(l), r); + } + function sload(l:storage(address)) -> address { + return StorageType.sload(Typedef.rep(l)); + } +} + +forall k v. + instance mapping(k,v):Storable(mapping(k,v)) { + function store(l:storage(mapping(k,v)), r:mapping(k,v)) -> () { + // StorageType.store(Typedef.rep(l), r); + unimplemented(); + } + function sload(l:storage(mapping(k,v))) -> mapping(k,v) { + // return StorageType.sload(Typedef.rep(l)); + return unimplemented(); + } +} + instance memory(string):Storable(string) { function store(dst:storage(string), src:memory(string)) -> () { diff --git a/test/examples/spec/129miniERC20.solc b/test/examples/spec/129miniERC20.solc new file mode 100644 index 00000000..3a46880c --- /dev/null +++ b/test/examples/spec/129miniERC20.solc @@ -0,0 +1,115 @@ +import std; +import dispatch; + +function caller() -> address { + let res: word; + assembly { + res := caller() + } + return address(res); +} + +function myrevert(msg: word) -> () { + assembly { mstore(0, msg) revert(0, 32) } +} + +function require(cond: bool, msg: word ) { + if( !cond ) { myrevert(msg); } +} + +contract MiniERC20 { + reserved : word; // forge idiosyncrasies + name : string; + symbol : memory(string); + owner : address; + decimals : uint256; + totalSupply : uint256; + balances : mapping(address,uint256); + allowance : mapping(address, mapping(address, uint256)); + + + constructor(name_ : memory(string), symbol_ : memory(string), totalSupply_:uint256) { + name = name_; + symbol = symbol_; + owner = caller(); + decimals = uint256(18); + owner = caller(); + mint(totalSupply_); + } + + + function getName() -> memory(string) { + return name; + } + + function mint(amount:uint256) -> () { + balances[owner] = Num.add(balances[owner], amount); + totalSupply = Num.add(totalSupply, amount); + } + +/* // original: + function transferFrom(address src, address dst, uint256 amt) public returns (bool) { + require(balanceOf[src] >= amt, "token/insufficient-balance"); + if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { + require(allowance[src][msg.sender] >= amt, "token/insufficient-allowance"); + allowance[src][msg.sender] -= amt; + } + + balanceOf[src] -= amt; + balanceOf[dst] += amt; + emit Transfer(src, dst, amt); + return true; + } +*/ + + function transferFrom(src:address, dst:address, amt:uint256) -> bool { + let msg_sender = caller(); + require( balances[src] >= amt /* "token/insufficient-balance" */ + , 0x746f6b656e2f696e73756666696369656e742d62616c616e6365 + ); + + if (src != msg_sender && allowance[src][msg_sender] != (Num.maxVal():uint256)) { + require( allowance[src][msg_sender] >= amt /* "token/insufficient-allowance" */ + , 0x746f6b656e2f696e73756666696369656e742d616c6c6f77616e6365 + ); + allowance[src][msg_sender] -= amt; + } + balances[src] = balances[src] - amt; + balances[dst] = balances[dst] + amt; + return true; + } + +/* + function approve(address usr, uint256 amt) public returns (bool) { + allowance[msg.sender][usr] = amt; + emit Approval(msg.sender, usr, amt); + return true; + } +*/ + + function approve(usr: address, amt: uint256) -> bool { + let msg_sender = caller(); + allowance[msg_sender][usr] = amt; + // emit Approval(msg.sender, usr, amt); + return true; + + } + +/* + function init() -> () { + owner = address(0x123456789abcdef); + decimals = Num.fromWord(18) : uint256; + } +*/ +/* + function main() -> uint256 { + let msg_sender = caller(); + init(); + mint(uint256(1000)); + allowance[owner][msg_sender] = uint256(1000); + transferFrom(owner, msg_sender, uint256(42)); + + return allowance[owner][msg_sender]; + } +*/ +} From 65cef6ff82464f36d2a9afa487964e4149500a1f Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Mon, 3 Nov 2025 20:19:38 +0100 Subject: [PATCH 05/11] desugaring hack to allow field types of type string --- src/Solcore/Frontend/Syntax/ElabTree.hs | 9 ++++++--- test/examples/spec/129miniERC20.solc | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Solcore/Frontend/Syntax/ElabTree.hs b/src/Solcore/Frontend/Syntax/ElabTree.hs index b38ebaaf..62d6a0fa 100644 --- a/src/Solcore/Frontend/Syntax/ElabTree.hs +++ b/src/Solcore/Frontend/Syntax/ElabTree.hs @@ -263,10 +263,13 @@ extraTopDeclsForContract c@(S.Contract cname ts decls) = do -- the types of previous fields are needed to construct field offset contractFieldStep :: Field Name -> ([Ty], [TopDecl Name]) -> ([Ty], [TopDecl Name]) contractFieldStep field (tys, decls) = (tys', decls') where - tys' = tys ++ [fieldTy field] + tys' = tys ++ [translateFieldType(fieldTy field)] decls' = decls ++ extraTopDeclsForContractField cname field offset offset = foldr pair unit tys +translateFieldType :: Ty -> Ty +translateFieldType string@(TyCon (Name "string") []) = TyCon "memory" [string] +translateFieldType t = t extraTopDeclsForContractField :: ContractName -> Field Name -> Ty -> [TopDecl Name] extraTopDeclsForContractField cname field@(Field fname fty _minit) offset = [selDecl, TInstDef sfInstance] where @@ -281,7 +284,7 @@ extraTopDeclsForContractField cname field@(Field fname fty _minit) offset = [sel , instVars = [] , instContext = [] , instName = "StructField" - , paramsTy = [fty, offset] + , paramsTy = [translateFieldType fty, offset] , mainTy = TyCon "StructField" [ctxTy, selType] , instFunctions = [] } @@ -645,7 +648,7 @@ instance Elab S.Exp where -- condition for valid constructor use if isCon && isNothing me' then pure (Con n es') - else if isClass then do + else if isClass then do pure (Call Nothing (mkClassName me' n) es') -- condition for function call else pure (Call me' n es') diff --git a/test/examples/spec/129miniERC20.solc b/test/examples/spec/129miniERC20.solc index 3a46880c..3e69c1d5 100644 --- a/test/examples/spec/129miniERC20.solc +++ b/test/examples/spec/129miniERC20.solc @@ -20,7 +20,7 @@ function require(cond: bool, msg: word ) { contract MiniERC20 { reserved : word; // forge idiosyncrasies name : string; - symbol : memory(string); + symbol : string; owner : address; decimals : uint256; totalSupply : uint256; From a79543e0d1364c30527ded45f81cb064bdd1fd75 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Tue, 4 Nov 2025 11:01:00 +0100 Subject: [PATCH 06/11] add missing stringid test --- test/examples/dispatch/stringid.solc | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/examples/dispatch/stringid.solc diff --git a/test/examples/dispatch/stringid.solc b/test/examples/dispatch/stringid.solc new file mode 100644 index 00000000..972bcc30 --- /dev/null +++ b/test/examples/dispatch/stringid.solc @@ -0,0 +1,41 @@ +import dispatch; + +contract C { + constructor() {} + function id(x:memory(string)) -> (memory(string)) { + let ptr : word = Typedef.rep(x); + let len : word; + let n1 : word; + assembly { + len := mload(ptr) + n1 := mload(add(ptr,32)) + } + log1(len, 0xc001); + log1(n1, 0xc002); + + return x; + } + + function const_a() -> (memory(string)) { + let resPtr = allocate_memory(64); + let payload = 0x7777777777777777777777777777777777777777777777777777777777777777; + mstore(resPtr, 3); + mstore(resPtr+32, payload); + return memory(resPtr); + } + function mylen(x:memory(string)) -> uint256 { + let ptr : word = Typedef.rep(x); + let l : word; + let n1 : word; + assembly { + l := mload(ptr) + n1 := mload(add(ptr,32)) + } + // log1(l, 0xc001); + // log1(n1, 0xc002); + + return uint256(l); + } + + // function answer() -> uint256 { return uint256(17); } +} From e7e2cf518bf2d0208c04a706cbc6a449a4bf89c5 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Tue, 4 Nov 2025 13:47:21 +0100 Subject: [PATCH 07/11] storage(string) : CanStore(memory(string)) --- src/Solcore/Frontend/Syntax/ElabTree.hs | 5 +- std/std.solc | 71 +++++++++++++------------ 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/Solcore/Frontend/Syntax/ElabTree.hs b/src/Solcore/Frontend/Syntax/ElabTree.hs index 62d6a0fa..f797d5f8 100644 --- a/src/Solcore/Frontend/Syntax/ElabTree.hs +++ b/src/Solcore/Frontend/Syntax/ElabTree.hs @@ -268,8 +268,9 @@ extraTopDeclsForContract c@(S.Contract cname ts decls) = do offset = foldr pair unit tys translateFieldType :: Ty -> Ty -translateFieldType string@(TyCon (Name "string") []) = TyCon "memory" [string] -translateFieldType t = t +-- translateFieldType string@(TyCon (Name "string") []) = TyCon "memory" [string] +translateFieldType t = TyCon "storage" [t] +--translateFieldType t = t extraTopDeclsForContractField :: ContractName -> Field Name -> Ty -> [TopDecl Name] extraTopDeclsForContractField cname field@(Field fname fty _minit) offset = [selDecl, TInstDef sfInstance] where diff --git a/std/std.solc b/std/std.solc index 9942fc25..e2246a43 100644 --- a/std/std.solc +++ b/std/std.solc @@ -1107,8 +1107,8 @@ forall baseType baseType_decoded reader . ABIDecoder(baseType, CalldataWordReade */ -pragma no-patterson-condition RVA; -pragma no-coverage-condition MemberAccessProxy, LVA, RVA, StructField; +pragma no-patterson-condition RVA, Assign; +pragma no-coverage-condition MemberAccessProxy, LVA, RVA, StructField, Assign; pragma no-bounded-variable-condition LVA, RVA; // -- storage @@ -1279,12 +1279,12 @@ forall cxt fieldSelector fieldType offsetType } } */ -forall cxt fieldSelector fieldType offsetType storageType -. StructField(ContractStorage(cxt), fieldSelector) : StructField (fieldType, offsetType) +forall cxt fieldSelector loadType offsetType storageType +. StructField(ContractStorage(cxt), fieldSelector) : StructField (storage(storageType), offsetType) , offsetType : StorageSize -, fieldType : Storable(storageType) -=> instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, offsetType) : LVA (storage(storageType)) { - function acc (x : MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, offsetType)) -> storage(storageType) { +, storage(storageType): CanStore(loadType) +=> instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, loadType, offsetType) : LVA (storage(storageType)) { + function acc (x : MemberAccessProxy(ContractStorage(cxt), fieldSelector, loadType, offsetType)) -> storage(storageType) { let offset : word = StorageSize.size(Proxy : Proxy(offsetType)) ; return storage(offset):storage(storageType); } @@ -1302,18 +1302,19 @@ forall cxt fieldSelector fieldType offsetType } */ -forall cxt fieldSelector fieldType offsetType storageType - . StructField(ContractStorage(cxt), fieldSelector):StructField(fieldType, offsetType) - , fieldType:Storable(storageType) +forall cxt fieldSelector loadType offsetType storageType + . StructField(ContractStorage(cxt), fieldSelector):StructField(storage(storageType), offsetType) + , storage(storageType):CanStore(loadType) // , fieldType:StorageType , offsetType:StorageSize - => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, offsetType):RVA(fieldType) { - function acc(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, fieldType, offsetType)) -> fieldType { + => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, loadType, offsetType):RVA(loadType) { + function acc(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, loadType, offsetType)) -> loadType { let offset:word = StorageSize.size(Proxy:Proxy(offsetType)); - return Storable.sload(storage(offset):storage(storageType)):fieldType; + return CanStore.sload(storage(offset):storage(storageType)):loadType; } } +/* forall structType fieldSelector fieldType storageType offsetType . StructField(structType, fieldSelector):StructField(fieldType, offsetType) , offsetType:StorageSize @@ -1327,8 +1328,8 @@ forall structType fieldSelector fieldType storageType offsetType return storage(ptr); } } - - +*/ +/* forall structType fieldSelector fieldType storageType offsetType . StructField(structType, fieldSelector):StructField(fieldType, offsetType) , offsetType:StorageSize @@ -1340,10 +1341,10 @@ forall structType fieldSelector fieldType storageType offsetType assembly { ptr := add(ptr, size) } - return StorageType.sload(ptr); + return CanStore.sload(ptr); } } - +*/ @@ -1392,24 +1393,26 @@ class lhs:Assign(rhs) { function assign(l:lhs, r:rhs) -> (); } -// a can be stored in storage(b); e.g. memory(string) : Storable(string) -pragma no-coverage-condition Storable, Assign; + +// a can store b; e.g. storage(string) : memory(string) +// pragma no-coverage-condition CanStore, Assign; forall a b. -class a:Storable(b) { - function store(r:storage(b), v:a); - function sload(r:storage(b)) -> a; +class a:CanStore(b) { + function store(r:a, v:b); + function sload(r:a) -> b; } -forall a b. a:Storable(b) => -instance storage(b):Assign(a) { - function assign(l:storage(b), r:a) -> () { - Storable.store(l, r); +forall a b. a:CanStore(b) => +instance a:Assign(b) { + function assign(l:a, r:b) -> () { + CanStore.store(l, r); } } + /* forall a. a:StorageType => -default instance a:Storable(a) { +default instance a:CanStore(a) { function store(l:storage(a), r:a) -> () { StorageType.store(Typedef.rep(l), r); } @@ -1419,7 +1422,7 @@ default instance a:Storable(a) { } */ - instance word:Storable(word) { + instance storage(word):CanStore(word) { function store(l:storage(word), r:word) -> () { StorageType.store(Typedef.rep(l), r); } @@ -1428,7 +1431,7 @@ default instance a:Storable(a) { } } - instance uint256:Storable(uint256) { + instance storage(uint256):CanStore(uint256) { function store(l:storage(uint256), r:uint256) -> () { StorageType.store(Typedef.rep(l), r); } @@ -1437,7 +1440,7 @@ default instance a:Storable(a) { } } - instance address:Storable(address) { + instance storage(address):CanStore(address) { function store(l:storage(address), r:address) -> () { StorageType.store(Typedef.rep(l), r); } @@ -1447,19 +1450,19 @@ default instance a:Storable(a) { } forall k v. - instance mapping(k,v):Storable(mapping(k,v)) { - function store(l:storage(mapping(k,v)), r:mapping(k,v)) -> () { + instance storage(mapping(k,v)):CanStore(storage(mapping(k,v))) { + function store(l:storage(mapping(k,v)), r:storage(mapping(k,v))) -> () { // StorageType.store(Typedef.rep(l), r); unimplemented(); } - function sload(l:storage(mapping(k,v))) -> mapping(k,v) { + function sload(l:storage(mapping(k,v))) -> storage(mapping(k,v)) { // return StorageType.sload(Typedef.rep(l)); return unimplemented(); } } -instance memory(string):Storable(string) { +instance storage(string):CanStore(memory(string)) { function store(dst:storage(string), src:memory(string)) -> () { let srcPtr : word = Typedef.rep(src); let slot = Typedef.rep(dst); From 79bc4962feac826024d5cc2b6032b21f8ea92d86 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Tue, 4 Nov 2025 17:15:05 +0100 Subject: [PATCH 08/11] 129miniERC20: more methods, cleanup --- test/examples/spec/129miniERC20.solc | 61 +++++++++------------------- 1 file changed, 19 insertions(+), 42 deletions(-) diff --git a/test/examples/spec/129miniERC20.solc b/test/examples/spec/129miniERC20.solc index 3e69c1d5..fb4e7d89 100644 --- a/test/examples/spec/129miniERC20.solc +++ b/test/examples/spec/129miniERC20.solc @@ -33,34 +33,37 @@ contract MiniERC20 { symbol = symbol_; owner = caller(); decimals = uint256(18); - owner = caller(); mint(totalSupply_); } + function test() -> uint256 { + approve(address(0), uint256(10)); + transferFrom(caller(), address(0), uint256(958)); + return getMyBalance(); + } function getName() -> memory(string) { return name; } + + function getMyBalance() -> uint256 { + return balances[caller()]; + } + + + function getMyAllowance() -> uint256 { + return allowance[owner][address(0)]; + } + function mint(amount:uint256) -> () { balances[owner] = Num.add(balances[owner], amount); totalSupply = Num.add(totalSupply, amount); } -/* // original: - function transferFrom(address src, address dst, uint256 amt) public returns (bool) { - require(balanceOf[src] >= amt, "token/insufficient-balance"); - if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { - require(allowance[src][msg.sender] >= amt, "token/insufficient-allowance"); - allowance[src][msg.sender] -= amt; - } - - balanceOf[src] -= amt; - balanceOf[dst] += amt; - emit Transfer(src, dst, amt); - return true; - } -*/ + function transfer(dst : address, amt : uint256) -> bool { + return transferFrom(caller(), dst, amt); + } function transferFrom(src:address, dst:address, amt:uint256) -> bool { let msg_sender = caller(); @@ -76,17 +79,10 @@ contract MiniERC20 { } balances[src] = balances[src] - amt; balances[dst] = balances[dst] + amt; + // emit Transfer(src, dst, amt); return true; } -/* - function approve(address usr, uint256 amt) public returns (bool) { - allowance[msg.sender][usr] = amt; - emit Approval(msg.sender, usr, amt); - return true; - } -*/ - function approve(usr: address, amt: uint256) -> bool { let msg_sender = caller(); allowance[msg_sender][usr] = amt; @@ -94,22 +90,3 @@ contract MiniERC20 { return true; } - -/* - function init() -> () { - owner = address(0x123456789abcdef); - decimals = Num.fromWord(18) : uint256; - } -*/ -/* - function main() -> uint256 { - let msg_sender = caller(); - init(); - mint(uint256(1000)); - allowance[owner][msg_sender] = uint256(1000); - transferFrom(owner, msg_sender, uint256(42)); - - return allowance[owner][msg_sender]; - } -*/ -} From 2a27317c4e7529328164dc5f7bc2dba8de2dc895 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Wed, 5 Nov 2025 11:22:37 +0100 Subject: [PATCH 09/11] dispatch/miniERC20 test --- test/Cases.hs | 1 + .../miniERC20.solc} | 43 +++++++++++++------ 2 files changed, 30 insertions(+), 14 deletions(-) rename test/examples/{spec/129miniERC20.solc => dispatch/miniERC20.solc} (80%) diff --git a/test/Cases.hs b/test/Cases.hs index 6d9038ca..0904b901 100644 --- a/test/Cases.hs +++ b/test/Cases.hs @@ -62,6 +62,7 @@ dispatches = "Files for dispatch cases" [ runDispatchTest "basic.solc" , runDispatchTest "stringid.solc" + , runDispatchTest "miniERC20.solc" ] where runDispatchTest file = runTestForFileWith (emptyOption mempty) file "./test/examples/dispatch" diff --git a/test/examples/spec/129miniERC20.solc b/test/examples/dispatch/miniERC20.solc similarity index 80% rename from test/examples/spec/129miniERC20.solc rename to test/examples/dispatch/miniERC20.solc index fb4e7d89..18ce3d89 100644 --- a/test/examples/spec/129miniERC20.solc +++ b/test/examples/dispatch/miniERC20.solc @@ -18,16 +18,14 @@ function require(cond: bool, msg: word ) { } contract MiniERC20 { - reserved : word; // forge idiosyncrasies name : string; symbol : string; owner : address; - decimals : uint256; + decimals : uint256; // should be uint8 when we get to it totalSupply : uint256; balances : mapping(address,uint256); allowance : mapping(address, mapping(address, uint256)); - constructor(name_ : memory(string), symbol_ : memory(string), totalSupply_:uint256) { name = name_; symbol = symbol_; @@ -36,24 +34,28 @@ contract MiniERC20 { mint(totalSupply_); } - - function test() -> uint256 { - approve(address(0), uint256(10)); - transferFrom(caller(), address(0), uint256(958)); - return getMyBalance(); - } - function getName() -> memory(string) { + function name() -> memory(string) { return name; } + function symbol() -> memory(string) { + return symbol; + } - function getMyBalance() -> uint256 { - return balances[caller()]; + function decimals() -> uint256 { + return decimals; + } + + function allowance(owner_ : address, spender: address) -> uint256 { + return allowance[owner_][spender]; // don't use "owner" here } + function balanceOf(account : address) -> uint256 { + return balances[account]; + } - function getMyAllowance() -> uint256 { - return allowance[owner][address(0)]; + function totalSupply() -> uint256 { + return totalSupply; } function mint(amount:uint256) -> () { @@ -88,5 +90,18 @@ contract MiniERC20 { allowance[msg_sender][usr] = amt; // emit Approval(msg.sender, usr, amt); return true; + } + + + // testing + function getMyBalance() -> uint256 { + return balances[caller()]; + } + function test() -> uint256 { + approve(address(0), uint256(10)); + transferFrom(caller(), address(0), uint256(958)); + return getMyBalance(); } + +} \ No newline at end of file From 7fcc4067e34d674cd96916aa8647f2bcbf4044ef Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Wed, 5 Nov 2025 11:37:30 +0100 Subject: [PATCH 10/11] clean up ElabTree --- src/Solcore/Frontend/Syntax/ElabTree.hs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Solcore/Frontend/Syntax/ElabTree.hs b/src/Solcore/Frontend/Syntax/ElabTree.hs index f797d5f8..5562b7e1 100644 --- a/src/Solcore/Frontend/Syntax/ElabTree.hs +++ b/src/Solcore/Frontend/Syntax/ElabTree.hs @@ -263,14 +263,12 @@ extraTopDeclsForContract c@(S.Contract cname ts decls) = do -- the types of previous fields are needed to construct field offset contractFieldStep :: Field Name -> ([Ty], [TopDecl Name]) -> ([Ty], [TopDecl Name]) contractFieldStep field (tys, decls) = (tys', decls') where - tys' = tys ++ [translateFieldType(fieldTy field)] + tys' = tys ++ [fieldTy field] decls' = decls ++ extraTopDeclsForContractField cname field offset offset = foldr pair unit tys translateFieldType :: Ty -> Ty --- translateFieldType string@(TyCon (Name "string") []) = TyCon "memory" [string] translateFieldType t = TyCon "storage" [t] ---translateFieldType t = t extraTopDeclsForContractField :: ContractName -> Field Name -> Ty -> [TopDecl Name] extraTopDeclsForContractField cname field@(Field fname fty _minit) offset = [selDecl, TInstDef sfInstance] where From d0d0e2b42d49abfc5302bb1bf3f448a04e1a2f91 Mon Sep 17 00:00:00 2001 From: Marcin Benke Date: Wed, 5 Nov 2025 11:42:21 +0100 Subject: [PATCH 11/11] cleanup std --- std/std.solc | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/std/std.solc b/std/std.solc index e2246a43..0ee513b7 100644 --- a/std/std.solc +++ b/std/std.solc @@ -1249,7 +1249,7 @@ instance uint256:StorageType { // -- structure fields (including contract fields) - forall self fieldType offsetType. +forall self fieldType offsetType. class self:StructField(fieldType, offsetType) {} data StructField(structType, fieldSelector) = StructField(structType); @@ -1267,18 +1267,7 @@ function memberAccessBase(x:MemberAccessProxy(a, field, fieldType, offset)) -> // ------------------------------------------------------------------ // Contract field access // ------------------------------------------------------------------ -// FIXME: strings / Storable -/* -forall cxt fieldSelector fieldType offsetType - . StructField(ContractStorage(cxt), fieldSelector):StructField(fieldType, offsetType) - , offsetType:StorageSize - => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType):LVA(storage(fieldType)) { - function acc(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType)) -> storage(fieldType) { - let offset:word = StorageSize.size(Proxy:Proxy(offsetType)); - return storage(offset); - } -} -*/ + forall cxt fieldSelector loadType offsetType storageType . StructField(ContractStorage(cxt), fieldSelector) : StructField (storage(storageType), offsetType) , offsetType : StorageSize @@ -1289,23 +1278,10 @@ forall cxt fieldSelector loadType offsetType storageType return storage(offset):storage(storageType); } } -/* -forall cxt fieldSelector fieldType offsetType - . StructField(ContractStorage(cxt), fieldSelector):StructField(fieldType, storageType, offsetType) - , fieldType:StorageType - , offsetType:StorageSize - => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType):RVA(fieldType) { - function acc(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, offsetType)) -> fieldType { - let offset:word = StorageSize.size(Proxy:Proxy(offsetType)); - return StorageType.sload(offset):fieldType; - } -} -*/ forall cxt fieldSelector loadType offsetType storageType . StructField(ContractStorage(cxt), fieldSelector):StructField(storage(storageType), offsetType) , storage(storageType):CanStore(loadType) -// , fieldType:StorageType , offsetType:StorageSize => instance MemberAccessProxy(ContractStorage(cxt), fieldSelector, loadType, offsetType):RVA(loadType) { function acc(x:MemberAccessProxy(ContractStorage(cxt), fieldSelector, loadType, offsetType)) -> loadType { @@ -1314,6 +1290,7 @@ forall cxt fieldSelector loadType offsetType storageType } } +// TODO: structures other than contract context /* forall structType fieldSelector fieldType storageType offsetType . StructField(structType, fieldSelector):StructField(fieldType, offsetType) @@ -1328,8 +1305,7 @@ forall structType fieldSelector fieldType storageType offsetType return storage(ptr); } } -*/ -/* + forall structType fieldSelector fieldType storageType offsetType . StructField(structType, fieldSelector):StructField(fieldType, offsetType) , offsetType:StorageSize @@ -1388,14 +1364,14 @@ function rval(x:a) -> b { } +// TODO: consider merging CanStore and Assign forall lhs rhs. class lhs:Assign(rhs) { function assign(l:lhs, r:rhs) -> (); } -// a can store b; e.g. storage(string) : memory(string) -// pragma no-coverage-condition CanStore, Assign; +// a can store b; e.g. storage(string) : memory(string) forall a b. class a:CanStore(b) { function store(r:a, v:b);