├── .gitignore ├── .gitmodules ├── foundry.toml ├── .github └── workflows │ └── test.yml ├── README.md ├── test └── Solarray.t.sol └── generator.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Dotenv file 11 | .env 12 | 13 | remappings.txt -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | [submodule "lib/forge-std"] 5 | path = lib/forge-std 6 | url = https://github.com/foundry-rs/forge-std 7 | branch = v1.5.2 8 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | remappings = ['forge-std/=lib/forge-std/src/','ds-test/=lib/ds-test/src/'] 6 | 7 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solarray 🥀 2 | 3 | Wish Solidity had more concise array initialization? 4 | Wish you could concisely combine two arrays? 5 | Wish you could concisely make copies of arrays? 6 | Wish you could dynamically resize arrays? 7 | Wish you could dynamically resize arrays without making a copy? 8 | Wish you could pop the last element off an array? 9 | 10 | 11 | *Solarray* is a blazing-fast helper library for convenient Solidity dynamic arrays. Useful for writing `foundry` test cases that involve arrays. 12 | 13 | ## Usage 14 | 15 | ```solidity 16 | // concisely initialize dynamic arrays of fixed length 17 | uint8[] memory nums = Solarray.uint8s(1, 2, 3); 18 | uint8[] memory nums2 = Solarray.uint8s(4, 5, 6); 19 | 20 | // concisely combine two arrays into a new array using extend 21 | uint8[] memory combined = Solarray.extend(nums, nums2); 22 | 23 | // concisely create a copy of an array with an extra element using append 24 | uint8[] memory nums3 = Solarray.append(nums, 4); 25 | 26 | // use allocate to create a dynamic array that can be appended to later, up to a maximum length 27 | // note that this will not revert if you attempt to append more than the maximum length 28 | uint8[] memory nums = Solarray.allocateUints({maxLength: 3}); 29 | Solarray.appendUnsafe(nums, 1); 30 | Solarray.appendUnsafe(nums, 2); 31 | Solarray.appendUnsafe(nums, 3); 32 | 33 | // concisely initialize an array that you can then append to later 34 | nums = Solarray.uint8sWithMaxLength({maxLength:5, a:1, b:2, c:3}); 35 | 36 | // use truncate to shrink an array in-place; will revert with OutOfOffset if the new length is greater than the current length 37 | Solarray.truncate(nums, 2); 38 | // won't revert, slightly cheaper 39 | Solarray.truncateUnsafe(nums, 1); 40 | 41 | // copy an array 42 | uint8[] memory numsCopy = Solarray.copy(nums); 43 | 44 | // copy an array with a new length, longer or shorter. Longer will result in empty elements. 45 | numsCopy = Solarray.copyAndResize(nums, 5); 46 | numsCopy = Solarray.copyAndResize(nums, 1); 47 | 48 | // make a copy of an array with a new maximum length allocation 49 | // will result in an array of the same length. 50 | numsCopy = Solarray.copyAndAllocate(nums, 5); 51 | 52 | // pop the last element off an array. Will revert with OutOfOffset if the array is empty. 53 | uint8 last = Solarray.pop(nums2); 54 | // use unsafe when you are sure the array is not empty, for slight gas savings 55 | last = Solarray.popUnsafe(nums2); 56 | 57 | // pop the first element from an array. Will revert with OutOfOffset if the array is empty. 58 | // Returns the pointer to the modified array, and the popped element. 59 | uint8 first; 60 | (nums2, first) = Solarray.popLeft(nums2); 61 | // use unsafe when you are sure the array is not empty, for slight gas savings 62 | (nums2, first) = Solarray.popLeftUnsafe(nums2); 63 | 64 | // convert from a fixed length array to a dynamic array 65 | uint8[] memory numsFromFixed = Solarray.fromFixed([1, 2, 3]); 66 | // also allocate a max length for the new dynamic array 67 | numsFromFixed = Solarray.fromFixedWithMaxLength([1, 2, 3], 5); 68 | ``` 69 | 70 | Supports 1-8 arguments for most of the common types. 71 | 72 | ## Installation 73 | 74 | ```sh 75 | forge install emo-eth/solarray 76 | ``` 77 | 78 | ## Usage 79 | 80 | ```sh 81 | import {Solarray} from "solarray/solarray.sol"; 82 | ``` 83 | 84 | ## Adding new types 85 | 86 | If you want to include a new type, you can use the [generator.py](https://github.com/emo-eth/solarray/blob/master/generator.py) script that 87 | I used to make the helper library. 88 | -------------------------------------------------------------------------------- /test/Solarray.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/Solarray.sol"; 6 | 7 | contract SolarrayTest is Test { 8 | function setUp() public {} 9 | 10 | function testUint8s() public { 11 | uint8[] memory uint8s = Solarray.uint8s(0, 1, 2); 12 | assertEq(uint8s.length, 3); 13 | assertEq(uint8s[0], 0); 14 | assertEq(uint8s[1], 1); 15 | assertEq(uint8s[2], 2); 16 | } 17 | 18 | function testUint256s() public { 19 | uint256[] memory uint256s = Solarray.uint256s(0, 1, 2); 20 | assertEq(uint256s.length, 3); 21 | assertEq(uint256s[0], 0); 22 | assertEq(uint256s[1], 1); 23 | assertEq(uint256s[2], 2); 24 | } 25 | 26 | function testStrings() public { 27 | string[] memory strings = Solarray.strings("a", "b", "c", "d"); 28 | assertEq(strings.length, 4); 29 | assertEq(strings[0], "a"); 30 | assertEq(strings[1], "b"); 31 | assertEq(strings[2], "c"); 32 | assertEq(strings[3], "d"); 33 | } 34 | 35 | function testAllocateAndAppendUnsafe() public { 36 | uint256[] memory uint256s = Solarray.allocateUints(3); 37 | uint256[] memory idk = new uint256[](3); 38 | 39 | uint256 i = 0; 40 | assertEmpty(idk, 3); 41 | assertContainsAscending(uint256s, i); 42 | 43 | i = 1; 44 | Solarray.appendUnsafe(uint256s, i); 45 | assertEmpty(idk, 3); 46 | assertContainsAscending(uint256s, i); 47 | 48 | i = 2; 49 | Solarray.appendUnsafe(uint256s, i); 50 | assertEmpty(idk, 3); 51 | assertContainsAscending(uint256s, i); 52 | 53 | i = 3; 54 | Solarray.appendUnsafe(uint256s, i); 55 | assertEmpty(idk, 3); 56 | assertContainsAscending(uint256s, i); 57 | 58 | i = 4; 59 | Solarray.appendUnsafe(uint256s, i); 60 | assertEq(idk.length, 4, "memory should be contaminated"); 61 | } 62 | 63 | function testAppendSafe() public { 64 | uint256[] memory uint256s; 65 | uint256[] memory idk = new uint256[](3); 66 | 67 | uint256 i = 0; 68 | assertEmpty(idk, 3); 69 | assertContainsAscending(uint256s, i); 70 | 71 | i = 1; 72 | uint256s = Solarray.append(uint256s, i); 73 | assertEmpty(idk, 3); 74 | assertContainsAscending(uint256s, i); 75 | 76 | i = 2; 77 | uint256s = Solarray.append(uint256s, i); 78 | assertEmpty(idk, 3); 79 | assertContainsAscending(uint256s, i); 80 | 81 | i = 3; 82 | uint256s = Solarray.append(uint256s, i); 83 | assertEmpty(idk, 3); 84 | assertContainsAscending(uint256s, i); 85 | 86 | i = 4; 87 | uint256s = Solarray.append(uint256s, i); 88 | assertEq(idk.length, 3, "memory should not be contaminated"); 89 | assertContainsAscending(uint256s, i); 90 | } 91 | 92 | function testExtend() public { 93 | uint256[] memory a = Solarray.uints(1, 2, 3); 94 | uint256[] memory b = Solarray.uints(4, 5, 6, 7); 95 | uint256[] memory c = Solarray.extend(a, b); 96 | assertContainsAscending(c, 7); 97 | assertEq(a.length, 3); 98 | assertEq(b.length, 4); 99 | } 100 | 101 | function testCopy() public { 102 | uint256[] memory a = Solarray.uints(1, 2, 3); 103 | uint256[] memory b = Solarray.copy(a); 104 | assertContainsAscending(b, 3); 105 | assertEq(a.length, 3); 106 | assertEq(b.length, 3); 107 | bool equal; 108 | assembly { 109 | equal := eq(a, b) 110 | } 111 | assertEq(equal, false, "memory pointer should be different"); 112 | } 113 | 114 | function testCopyAndResize() public { 115 | uint256[] memory a = Solarray.uints(1, 2, 3); 116 | uint256[] memory b = Solarray.copyAndResize(a, 5); 117 | assertEq(a.length, 3); 118 | assertEq(b.length, 5); 119 | bool equal; 120 | assembly { 121 | equal := eq(a, b) 122 | } 123 | assertEq(equal, false, "memory pointer should be different"); 124 | 125 | b = Solarray.copyAndResize(a, 2); 126 | assertEq(a.length, 3); 127 | assertEq(b.length, 2); 128 | assembly { 129 | equal := eq(a, b) 130 | } 131 | assertEq(equal, false, "memory pointer should be different"); 132 | assertContainsAscending(b, 2); 133 | } 134 | 135 | function testCopyAndAllocate() public { 136 | uint256[] memory a = Solarray.uints(1, 2, 3); 137 | uint256[] memory b = Solarray.copyAndAllocate(a, 4); 138 | uint256[] memory c = Solarray.uints(1, 2, 3); 139 | assertEq(a.length, 3); 140 | assertEq(b.length, 3); 141 | bool equal; 142 | assembly { 143 | equal := eq(a, b) 144 | } 145 | assertEq(equal, false, "memory pointer should be different"); 146 | 147 | b = Solarray.appendUnsafe(b, 4); 148 | assertEq(a.length, 3); 149 | assertEq(b.length, 4); 150 | assertContainsAscending(b, 4); 151 | b = Solarray.appendUnsafe(b, 5); 152 | assertEq(a.length, 3); 153 | assertContainsAscending(b, 5); 154 | assertEq(c.length, 5, "memory should have been dirtied"); 155 | } 156 | 157 | function testTruncate() public { 158 | uint256[] memory a = Solarray.uints(1, 2, 3, 4, 5); 159 | uint256[] memory b = Solarray.truncate(a, 3); 160 | assertEq(a.length, 3); 161 | assertEq(b.length, 3); 162 | assertContainsAscending(b, 3); 163 | bool equal; 164 | assembly { 165 | equal := eq(a, b) 166 | } 167 | assertEq(equal, true, "memory pointer should be the same"); 168 | } 169 | 170 | function testPop() public { 171 | uint256[] memory a = Solarray.uints(1, 2); 172 | uint256 value = Solarray.pop(a); 173 | assertEq(a.length, 1, "length should be 1"); 174 | assertEq(value, 2, "value should be 2"); 175 | value = Solarray.popUnsafe(a); 176 | assertEq(a.length, 0, "length should be 0"); 177 | assertEq(value, 1, "value should be 1"); 178 | 179 | vm.expectRevert( /*abi.encodeWithSignature("Panic(uint256)", 0x11)*/ ); 180 | this.extPop(a); 181 | } 182 | 183 | function testFromFixed() public { 184 | uint256[3] memory f = [uint256(1), uint256(2), uint256(3)]; 185 | uint256[] memory a = Solarray.fromFixed(f); 186 | assertContainsAscending(a, 3); 187 | a = Solarray.fromFixedWithMaxLength(f, 5); 188 | uint256[] memory b = Solarray.fromFixed(f); 189 | assertContainsAscending(a, 3); 190 | assertContainsAscending(b, 3); 191 | Solarray.appendUnsafe(a, 4); 192 | Solarray.appendUnsafe(a, 5); 193 | Solarray.appendUnsafe(a, 6); 194 | assertContainsAscending(a, 6); 195 | assertEq(b.length, 6, "memory should have been dirtied"); 196 | } 197 | 198 | function testPopLeft() public { 199 | uint256[] memory a = Solarray.uints(1, 2); 200 | uint256 value; 201 | (a, value) = Solarray.popLeft(a); 202 | assertEq(a.length, 1, "length should be 1"); 203 | assertEq(value, 1, "value should be 1"); 204 | (a, value) = Solarray.popLeftUnsafe(a); 205 | assertEq(a.length, 0, "length should be 0"); 206 | assertEq(value, 2, "value should be 2"); 207 | assertEq(a.length, 0, "length should be 0"); 208 | 209 | vm.expectRevert( /*abi.encodeWithSignature("Panic(uint256)", 0x11)*/ ); 210 | this.extPopLeft(a); 211 | } 212 | 213 | function extPop(uint256[] memory arr) external pure returns (uint256[] memory _arr, uint256 value) { 214 | _arr = arr; 215 | value = Solarray.pop(_arr); 216 | } 217 | 218 | function extPopLeft(uint256[] memory arr) external pure returns (uint256[] memory _arr, uint256 value) { 219 | (_arr, value) = Solarray.popLeft(arr); 220 | } 221 | 222 | function assertEmpty(uint256[] memory uint256s, uint256 expectedLength) internal { 223 | assertEq(uint256s.length, expectedLength, "not correct length"); 224 | for (uint256 i = 0; i < uint256s.length; i++) { 225 | assertEq(uint256s[i], 0, "not empty"); 226 | } 227 | } 228 | 229 | function assertContainsAscending(uint256[] memory uint256s, uint256 length) internal { 230 | assertEq(uint256s.length, length, "not correct length"); 231 | for (uint256 i = 0; i < length; i++) { 232 | assertEq(uint256s[i], i + 1, "not ascending"); 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /generator.py: -------------------------------------------------------------------------------- 1 | base = """// SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | library Solarray {{ 5 | {} 6 | }} 7 | """ 8 | 9 | types = [ 10 | "uint8", 11 | "uint16", 12 | "uint32", 13 | # "uint40", 14 | # "uint48", 15 | # "uint56", 16 | "uint64", 17 | # "uint72", 18 | # "uint80", 19 | # "uint88", 20 | # "uint96", 21 | # "uint104", 22 | # "uint112", 23 | # "uint120", 24 | "uint128", 25 | # "uint136", 26 | # "uint144", 27 | # "uint152", 28 | # "uint160", 29 | # "uint168", 30 | # "uint176", 31 | # "uint184", 32 | # "uint192", 33 | # "uint200", 34 | # "uint208", 35 | # "uint216", 36 | # "uint224", 37 | # "uint232", 38 | # "uint240", 39 | # "uint248", 40 | "uint256", 41 | "int8", 42 | "int16", 43 | "int32", 44 | # "int40", 45 | # "int48", 46 | # "int56", 47 | "int64", 48 | # "int72", 49 | # "int80", 50 | # "int88", 51 | # "int96", 52 | # "int104", 53 | # "int112", 54 | # "int120", 55 | "int128", 56 | # "int136", 57 | # "int144", 58 | # "int152", 59 | # "int160", 60 | # "int168", 61 | # "int176", 62 | # "int184", 63 | # "int192", 64 | # "int200", 65 | # "int208", 66 | # "int216", 67 | # "int224", 68 | # "int232", 69 | # "int240", 70 | # "int248", 71 | "int256", 72 | "bytes1", 73 | # "bytes2", 74 | # "bytes3", 75 | "bytes4", 76 | # "bytes5", 77 | # "bytes6", 78 | # "bytes7", 79 | "bytes8", 80 | # "bytes9", 81 | # "bytes10", 82 | # "bytes11", 83 | # "bytes12", 84 | # "bytes13", 85 | # "bytes14", 86 | # "bytes15", 87 | # "bytes16", 88 | # "bytes17", 89 | # "bytes18", 90 | # "bytes19", 91 | "bytes20", 92 | # "bytes21", 93 | # "bytes22", 94 | # "bytes23", 95 | # "bytes24", 96 | # "bytes25", 97 | # "bytes26", 98 | # "bytes27", 99 | # "bytes28", 100 | # "bytes29", 101 | # "bytes30", 102 | # "bytes31", 103 | "bytes32", 104 | "address", 105 | "bool", 106 | "bytes memory", 107 | "string memory", 108 | ] 109 | 110 | shorthand = ["uint", "int"] 111 | 112 | 113 | def get_suffix(t_name): 114 | if t_name[-2:] == "ss": 115 | return "es" 116 | elif t_name[-1] == "s": 117 | return "Array" 118 | else: 119 | return "s" 120 | 121 | 122 | def append_array_constructors(_type: str, functions: list[str], length: int): 123 | type_name = _type.split()[0] 124 | suffix = get_suffix(type_name) 125 | for i in range(1, length + 1): 126 | arguments = ",".join([f"{_type} {chr(ord('a') + j)}" for j in range(i)]) 127 | copying = "\n".join([f"\t\tarr[{j}] = {chr(ord('a') + j)};" for j in range(i)]) 128 | 129 | functions.append( 130 | f""" 131 | function {type_name}{suffix}({arguments}) internal pure returns ({type_name}[] memory) {{ 132 | {type_name}[] memory arr = new {type_name}[]({i}); 133 | {copying} 134 | return arr; 135 | }} 136 | """ 137 | ) 138 | 139 | 140 | def append_allocated_array_constructors(_type: str, functions: list[str], length: int): 141 | type_name = _type.split()[0] 142 | suffix = get_suffix(type_name) 143 | for i in range(1, length + 1): 144 | arguments = "uint256 maxLength, " + ",".join( 145 | [f"{_type} {chr(ord('a') + j)}" for j in range(i)] 146 | ) 147 | copying = "\n".join([f"\t\tarr[{j}] = {chr(ord('a') + j)};" for j in range(i)]) 148 | 149 | functions.append( 150 | f""" 151 | function {type_name}{suffix}WithMaxLength({arguments}) internal pure returns ({type_name}[] memory) {{ 152 | {type_name}[] memory arr = new {type_name}[](maxLength); 153 | assembly {{ 154 | mstore(arr, {i}) 155 | }} 156 | {copying} 157 | return arr; 158 | }} 159 | """ 160 | ) 161 | 162 | 163 | def append_extend(_type: str, functions: list[str]): 164 | type_name = _type.split()[0] 165 | functions.append( 166 | f""" 167 | function extend({type_name}[] memory arr1, {type_name}[] memory arr2) internal pure returns ({type_name}[] memory newArr) {{ 168 | uint256 length1 = arr1.length; 169 | uint256 length2 = arr2.length; 170 | newArr = new {type_name}[](length1+ length2); 171 | for (uint256 i = 0; i < length1;) {{ 172 | newArr[i] = arr1[i]; 173 | unchecked {{ 174 | ++i; 175 | }} 176 | }} 177 | for (uint256 i = 0; i < arr2.length;) {{ 178 | uint256 j; 179 | unchecked {{ 180 | j = i + length1; 181 | }} 182 | newArr[j] = arr2[i]; 183 | unchecked {{ 184 | ++i; 185 | }} 186 | }} 187 | }} 188 | """ 189 | ) 190 | 191 | 192 | def append_alloc(type: str, functions: list[str]): 193 | type_name = type.split()[0] 194 | capitalized_type_name = type_name[0].upper() + type_name[1:] 195 | suffix = get_suffix(type_name) 196 | functions.append( 197 | f""" 198 | function allocate{capitalized_type_name}{suffix}(uint256 length) internal pure returns ({type_name}[] memory arr) {{ 199 | arr = new {type_name}[](length); 200 | assembly {{ 201 | mstore(arr, 0) 202 | }} 203 | }} 204 | """ 205 | ) 206 | 207 | 208 | def append_append(_type: str, functions: list[str]): 209 | type_name = _type.split()[0] 210 | functions.append( 211 | f""" 212 | function append({type_name}[] memory arr, {_type} value) internal pure returns ({type_name}[] memory newArr) {{ 213 | uint256 length = arr.length; 214 | newArr = new {type_name}[](length + 1); 215 | newArr[length] = value; 216 | for (uint256 i = 0; i < length;) {{ 217 | newArr[i] = arr[i]; 218 | unchecked {{ 219 | ++i; 220 | }} 221 | }} 222 | }} 223 | 224 | function appendUnsafe({type_name}[] memory arr, {_type} value) internal pure returns ({type_name}[] memory modifiedArr) {{ 225 | uint256 length = arr.length; 226 | modifiedArr = arr; 227 | assembly {{ 228 | mstore(modifiedArr, add(length, 1)) 229 | mstore(add(modifiedArr, shl(5, add(length, 1))), value) 230 | }} 231 | }} 232 | """ 233 | ) 234 | 235 | 236 | def append_copy(_type: str, functions: list[str]): 237 | type_name = _type.split()[0] 238 | functions.append( 239 | f""" 240 | function copy({type_name}[] memory arr) internal pure returns ({type_name}[] memory newArr) {{ 241 | uint256 length = arr.length; 242 | newArr = new {type_name}[](length); 243 | for (uint256 i = 0; i < length;) {{ 244 | newArr[i] = arr[i]; 245 | unchecked {{ 246 | ++i; 247 | }} 248 | }} 249 | }} 250 | 251 | function copyAndResize({type_name}[] memory arr, uint256 newLength) internal pure returns ({type_name}[] memory newArr) {{ 252 | newArr = new {type_name}[](newLength); 253 | uint256 length = arr.length; 254 | // allow shrinking a copy without copying extra members 255 | length = (length > newLength) ? newLength : length; 256 | for (uint256 i = 0; i < length;) {{ 257 | newArr[i] = arr[i]; 258 | unchecked {{ 259 | ++i; 260 | }} 261 | }} 262 | // TODO: consider writing 0-pointer to the rest of the array if longer for dynamic elements 263 | }} 264 | 265 | function copyAndAllocate({type_name}[] memory arr, uint256 maxLength) internal pure returns ({type_name}[] memory newArr) {{ 266 | newArr = new {type_name}[](maxLength); 267 | uint256 originalLength = arr.length; 268 | for (uint256 i = 0; i < originalLength;) {{ 269 | newArr[i] = arr[i]; 270 | unchecked {{ 271 | ++i; 272 | }} 273 | }} 274 | assembly {{ 275 | mstore(newArr, originalLength) 276 | }} 277 | }} 278 | """ 279 | ) 280 | 281 | 282 | def append_truncate(_type: str, functions: list[str]): 283 | type_name = _type.split()[0] 284 | functions.append( 285 | f""" 286 | function truncate({type_name}[] memory arr, uint256 newLength) internal pure returns ({type_name}[] memory _arr) {{ 287 | // truncate the array 288 | assembly {{ 289 | let oldLength := mload(arr) 290 | returndatacopy(returndatasize(), returndatasize(), gt(newLength, oldLength)) 291 | mstore(arr, newLength) 292 | _arr := arr 293 | 294 | }} 295 | }} 296 | 297 | function truncateUnsafe({type_name}[] memory arr, uint256 newLength) internal pure returns ({type_name}[] memory _arr) {{ 298 | // truncate the array 299 | assembly {{ 300 | mstore(arr, newLength) 301 | _arr := arr 302 | }} 303 | }} 304 | """ 305 | ) 306 | 307 | 308 | def append_pop(_type: str, functions: list[str]): 309 | type_name = _type.split()[0] 310 | functions.append( 311 | f""" 312 | function pop({type_name}[] memory arr) internal pure returns ({_type} value) {{ 313 | assembly {{ 314 | let length := mload(arr) 315 | returndatacopy(returndatasize(),returndatasize(),iszero(length)) 316 | value := mload(add(arr, shl(5, length))) 317 | mstore(arr, sub(length, 1)) 318 | }} 319 | }} 320 | 321 | function popUnsafe({type_name}[] memory arr) internal pure returns ({_type} value) {{ 322 | // This function is unsafe because it does not check if the array is empty. 323 | assembly {{ 324 | let length := mload(arr) 325 | value := mload(add(arr, shl(5, length))) 326 | mstore(arr, sub(length, 1)) 327 | }} 328 | }} 329 | 330 | function popLeft({type_name}[] memory arr) internal pure returns ({type_name}[] memory newArr, {_type} value) {{ 331 | assembly {{ 332 | let length := mload(arr) 333 | returndatacopy(returndatasize(),returndatasize(),iszero(length)) 334 | value := mload(add(arr, 0x20)) 335 | newArr := add(arr, 0x20) 336 | mstore(newArr, sub(length, 1)) 337 | }} 338 | }} 339 | 340 | function popLeftUnsafe({type_name}[] memory arr) internal pure returns ({type_name}[] memory newArr, {_type} value) {{ 341 | // This function is unsafe because it does not check if the array is empty. 342 | assembly {{ 343 | let length := mload(arr) 344 | value := mload(add(arr, 0x20)) 345 | newArr := add(arr, 0x20) 346 | mstore(newArr, sub(length, 1)) 347 | }} 348 | }} 349 | """ 350 | ) 351 | 352 | 353 | def append_from_fixed(_type: str, functions: list[str]): 354 | length = 8 355 | type_name = _type.split()[0] 356 | for i in range(1, length + 1): 357 | functions.append( 358 | f""" 359 | function fromFixed({type_name}[{i}] memory arr) internal pure returns ({type_name}[] memory newArr) {{ 360 | newArr = new {type_name}[]({i}); 361 | for (uint256 i = 0; i < {i};) {{ 362 | newArr[i] = arr[i]; 363 | unchecked {{ 364 | ++i; 365 | }} 366 | }} 367 | }} 368 | 369 | function fromFixedWithMaxLength({type_name}[{i}] memory arr, uint256 maxLength) internal pure returns ({type_name}[] memory newArr) {{ 370 | newArr = new {type_name}[](maxLength); 371 | for (uint256 i = 0; i < {i};) {{ 372 | newArr[i] = arr[i]; 373 | unchecked {{ 374 | 375 | ++i; 376 | }} 377 | }} 378 | assembly {{ 379 | mstore(newArr, {i}) 380 | }} 381 | }} 382 | """ 383 | ) 384 | 385 | 386 | def generate_array_functions(): 387 | length = 8 388 | functions = [] 389 | for _type in types: 390 | append_array_constructors(_type, functions, length) 391 | append_allocated_array_constructors(_type, functions, length) 392 | append_extend(_type, functions) 393 | append_alloc(_type, functions) 394 | append_truncate(_type, functions) 395 | append_append(_type, functions) 396 | append_copy(_type, functions) 397 | append_pop(_type, functions) 398 | append_from_fixed(_type, functions) 399 | for _type in shorthand: 400 | append_array_constructors(_type, functions, length) 401 | append_allocated_array_constructors(_type, functions, length) 402 | append_alloc(_type, functions) 403 | 404 | print(base.format("\n".join(functions))) 405 | 406 | 407 | generate_array_functions() 408 | --------------------------------------------------------------------------------