Skip to content

Implement storage arrays#469

Draft
axic wants to merge 13 commits into
argotorg:mainfrom
axic:storage-arrays
Draft

Implement storage arrays#469
axic wants to merge 13 commits into
argotorg:mainfrom
axic:storage-arrays

Conversation

@axic

@axic axic commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

No description provided.

claude added 13 commits June 9, 2026 12:55
Adds an array(member, index) data type to std alongside mapping(member,
index), with parallel Typedef, StorageSize, CanStore, LValueIdxAccess
and RValueIdxAccess instances. Slot for arr[i] follows Solidity's
dynamic array layout: keccak256(p) + i. Also exports larr/rarr
helpers analogous to lidx/ridx.
Adds 129arraystorage.solc which constructs a storage(array(uint256,
uint256)) at a fixed slot, writes two elements via Assign + larr, and
reads them back via rarr. Registered alongside the storage mapping
tests in test/Cases.hs.
Drops the index type parameter from array since the index is always a
Typedef(word) value, leaving the element type as the sole parameter
(matching Solidity's T[] surface form). Updates the existing storage
test and adds a new test that defines a contract field
`arr : array(uint256)` and accesses it via larr/rarr.
Adds test/examples/dispatch/storage.{solc,json} which exercises a
MemberRegistry contract with a storage array of addresses:
- addMember(address) appends to the array
- removeMember(address) performs swap-pop, reverting if not found
- numberOfMembers() returns the count
- getMembers() returns the full address[]

The contract maintains a separate memberCount alongside the storage
array. It also defines an ABIEncode/ABIAttribs instance for
memory(DynArray(t)) (t:Typedef(word)) so that getMembers can return a
true dynamic array via the dispatch ABI encoder.
Adds an Array typeclass to std with length/setLength methods and an
instance for storage(array(t)) that reads/writes the slot itself (Solidity
storage layout: length at slot p, elements at keccak256(p) + i).

The dispatch test no longer carries a separate memberCount field — it
calls Array.length(members) and Array.setLength(members, n) instead.
- Array typeclass gains push/pop methods (with element type as a class
  parameter); push grows the length, pop shrinks it (and reverts on empty).
- Array bounds-check: LValueIdxAccess/RValueIdxAccess on storage(array(a))
  now consult Array.length and revert via out_of_bounds when i >= length.
- lidx/ridx become generic typeclass wrappers (LValueIdxAccess/
  RValueIdxAccess), so arr[i] works for both mappings and arrays via the
  shared FieldAccess desugaring; larr/rarr are dropped.
- NameResolution learns a small UFCS rule: when the receiver of a
  method-style call resolves to a contract field (`field.method(args)`)
  and no class is otherwise selected, search the class env for a
  uniquely-named method and rewrite to `Class.method(field, args)`. This
  enables `members.push(addr)`, `members.length()`, `members.pop()`.
- Tests updated accordingly: dispatch/storage.solc no longer carries a
  separate memberCount; spec/129arraystorage and spec/130arrayfield use
  push and the bounds-checked accessors.
memory(ty):ABIAttribs (for any ty:ABIAttribs) + DynArray(t):ABIAttribs
already derive it. Keep only the ABIEncode instance, which has no
generic alternative (the commented-out memory(ty):ABIEncode based on
MemoryType still fails patterson/coverage).
Switches removeMember from swap-pop to the user-specified pattern:
foundIdx starts at length() as a not-found sentinel, the scan keeps
going past the match (solcore has no break), then subsequent elements
are shifted one slot down to close the gap and pop() drops the
duplicated last item. Preserves order of remaining members.
The previous Array.push/pop bodies dispatched recursively through
Array.length/Array.setLength on the same instance, which the specialiser
appears to have trouble pinning down for the storage(array(t)):Array(t)
MPTC instance (it produced 'no resolution found for StorageType.store :
word -> array(address) -> ()'). Inlining the slot/length arithmetic with
sload_/sstore_/StorageType.store directly avoids the recursive class
dispatch entirely. Same inlining for the LValueIdxAccess bounds check
(also drops its previously redundant a:StorageType constraint).
Restructure to avoid MPTC inference issues on length/setLength/pop,
which never mention elem in their signatures. push, which actually
binds the element type via val:elem, lives in its own MPTC class
ArrayPush(elem). UFCS still finds each method via the class-method
search, so members.push(addr), members.length(), members.pop() all
keep working unchanged in the tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants