@0xsequence/catapult 1.3.16 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +12 -0
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/provenance.d.ts +3 -0
- package/dist/commands/provenance.d.ts.map +1 -0
- package/dist/commands/provenance.js +138 -0
- package/dist/commands/provenance.js.map +1 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +7 -4
- package/dist/commands/run.js.map +1 -1
- package/dist/lib/__tests__/deployer.spec.js +118 -1
- package/dist/lib/__tests__/deployer.spec.js.map +1 -1
- package/dist/lib/__tests__/network-utils.spec.js +53 -8
- package/dist/lib/__tests__/network-utils.spec.js.map +1 -1
- package/dist/lib/__tests__/provenance.spec.d.ts +2 -0
- package/dist/lib/__tests__/provenance.spec.d.ts.map +1 -0
- package/dist/lib/__tests__/provenance.spec.js +205 -0
- package/dist/lib/__tests__/provenance.spec.js.map +1 -0
- package/dist/lib/contracts/__tests__/repository.spec.js +243 -0
- package/dist/lib/contracts/__tests__/repository.spec.js.map +1 -1
- package/dist/lib/contracts/repository.d.ts +9 -1
- package/dist/lib/contracts/repository.d.ts.map +1 -1
- package/dist/lib/contracts/repository.js +93 -7
- package/dist/lib/contracts/repository.js.map +1 -1
- package/dist/lib/core/__tests__/assert-action.spec.d.ts +2 -0
- package/dist/lib/core/__tests__/assert-action.spec.d.ts.map +1 -0
- package/dist/lib/core/__tests__/assert-action.spec.js +377 -0
- package/dist/lib/core/__tests__/assert-action.spec.js.map +1 -0
- package/dist/lib/core/__tests__/engine.spec.js +80 -0
- package/dist/lib/core/__tests__/engine.spec.js.map +1 -1
- package/dist/lib/core/__tests__/loader.spec.js +29 -0
- package/dist/lib/core/__tests__/loader.spec.js.map +1 -1
- package/dist/lib/core/__tests__/resolver.spec.js +383 -0
- package/dist/lib/core/__tests__/resolver.spec.js.map +1 -1
- package/dist/lib/core/engine.d.ts.map +1 -1
- package/dist/lib/core/engine.js +33 -0
- package/dist/lib/core/engine.js.map +1 -1
- package/dist/lib/core/loader.d.ts +1 -0
- package/dist/lib/core/loader.d.ts.map +1 -1
- package/dist/lib/core/loader.js +6 -1
- package/dist/lib/core/loader.js.map +1 -1
- package/dist/lib/core/resolver.d.ts +2 -0
- package/dist/lib/core/resolver.d.ts.map +1 -1
- package/dist/lib/core/resolver.js +89 -0
- package/dist/lib/core/resolver.js.map +1 -1
- package/dist/lib/deployer.d.ts.map +1 -1
- package/dist/lib/deployer.js +21 -4
- package/dist/lib/deployer.js.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/parsers/__tests__/job.spec.js +77 -0
- package/dist/lib/parsers/__tests__/job.spec.js.map +1 -1
- package/dist/lib/parsers/__tests__/source.spec.d.ts +2 -0
- package/dist/lib/parsers/__tests__/source.spec.d.ts.map +1 -0
- package/dist/lib/parsers/__tests__/source.spec.js +121 -0
- package/dist/lib/parsers/__tests__/source.spec.js.map +1 -0
- package/dist/lib/parsers/index.d.ts +1 -0
- package/dist/lib/parsers/index.d.ts.map +1 -1
- package/dist/lib/parsers/index.js +1 -0
- package/dist/lib/parsers/index.js.map +1 -1
- package/dist/lib/parsers/job.d.ts.map +1 -1
- package/dist/lib/parsers/job.js +11 -0
- package/dist/lib/parsers/job.js.map +1 -1
- package/dist/lib/parsers/source.d.ts +4 -0
- package/dist/lib/parsers/source.d.ts.map +1 -0
- package/dist/lib/parsers/source.js +107 -0
- package/dist/lib/parsers/source.js.map +1 -0
- package/dist/lib/provenance.d.ts +34 -0
- package/dist/lib/provenance.d.ts.map +1 -0
- package/dist/lib/provenance.js +645 -0
- package/dist/lib/provenance.js.map +1 -0
- package/dist/lib/types/actions.d.ts +18 -2
- package/dist/lib/types/actions.d.ts.map +1 -1
- package/dist/lib/types/actions.js +1 -0
- package/dist/lib/types/actions.js.map +1 -1
- package/dist/lib/types/contracts.d.ts +3 -0
- package/dist/lib/types/contracts.d.ts.map +1 -1
- package/dist/lib/types/definitions.d.ts +1 -0
- package/dist/lib/types/definitions.d.ts.map +1 -1
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/index.d.ts.map +1 -1
- package/dist/lib/types/index.js +1 -0
- package/dist/lib/types/index.js.map +1 -1
- package/dist/lib/types/source.d.ts +24 -0
- package/dist/lib/types/source.d.ts.map +1 -0
- package/dist/lib/types/source.js +3 -0
- package/dist/lib/types/source.js.map +1 -0
- package/dist/lib/types/values.d.ts +33 -1
- package/dist/lib/types/values.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +3 -2
- package/src/commands/index.ts +2 -1
- package/src/commands/list.ts +14 -1
- package/src/commands/provenance.ts +120 -0
- package/src/commands/run.ts +11 -6
- package/src/lib/__tests__/deployer.spec.ts +177 -1
- package/src/lib/__tests__/network-utils.spec.ts +63 -14
- package/src/lib/__tests__/provenance.spec.ts +208 -0
- package/src/lib/contracts/__tests__/repository.spec.ts +270 -2
- package/src/lib/contracts/repository.ts +112 -14
- package/src/lib/core/__tests__/assert-action.spec.ts +474 -0
- package/src/lib/core/__tests__/engine.spec.ts +116 -0
- package/src/lib/core/__tests__/loader.spec.ts +34 -1
- package/src/lib/core/__tests__/resolver.spec.ts +444 -1
- package/src/lib/core/engine.ts +52 -0
- package/src/lib/core/loader.ts +8 -2
- package/src/lib/core/resolver.ts +116 -0
- package/src/lib/deployer.ts +28 -4
- package/src/lib/index.ts +4 -1
- package/src/lib/parsers/__tests__/job.spec.ts +81 -0
- package/src/lib/parsers/__tests__/source.spec.ts +134 -0
- package/src/lib/parsers/index.ts +1 -0
- package/src/lib/parsers/job.ts +14 -2
- package/src/lib/parsers/source.ts +129 -0
- package/src/lib/provenance.ts +785 -0
- package/src/lib/types/actions.ts +22 -1
- package/src/lib/types/contracts.ts +4 -1
- package/src/lib/types/definitions.ts +7 -0
- package/src/lib/types/index.ts +1 -0
- package/src/lib/types/source.ts +26 -0
- package/src/lib/types/values.ts +71 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ethers } from 'ethers'
|
|
2
2
|
import { ValueResolver } from '../resolver'
|
|
3
3
|
import { ExecutionContext } from '../context'
|
|
4
|
-
import { BasicArithmeticValue, Network, ReadBalanceValue, ComputeCreate2Value, ConstructorEncodeValue, AbiEncodeValue, AbiPackValue, CallValue, ContractExistsValue, ComputeCreateValue, SliceBytesValue } from '../../types'
|
|
4
|
+
import { BasicArithmeticValue, Network, ReadBalanceValue, GetStorageAtValue, ComputeSlotValue, ComputeCreate2Value, ConstructorEncodeValue, AbiEncodeValue, AbiPackValue, CallValue, ContractExistsValue, ComputeCreateValue, SliceBytesValue } from '../../types'
|
|
5
5
|
import { ContractRepository } from '../../contracts/repository'
|
|
6
6
|
|
|
7
7
|
describe('ValueResolver', () => {
|
|
@@ -2050,4 +2050,447 @@ describe('ValueResolver', () => {
|
|
|
2050
2050
|
expect(result).toBe('0x010203')
|
|
2051
2051
|
})
|
|
2052
2052
|
})
|
|
2053
|
+
|
|
2054
|
+
describe('get-storage-at', () => {
|
|
2055
|
+
const testAddress = '0x1234567890123456789012345678901234567890'
|
|
2056
|
+
const testSlot = '0x0000000000000000000000000000000000000000000000000000000000000000'
|
|
2057
|
+
const testSlotNumber = 0
|
|
2058
|
+
const expectedStorageValue = '0x0000000000000000000000000000000000000000000000000000000000000001'
|
|
2059
|
+
let anvilProvider: ethers.JsonRpcProvider
|
|
2060
|
+
|
|
2061
|
+
beforeEach(async () => {
|
|
2062
|
+
anvilProvider = context.provider as ethers.JsonRpcProvider
|
|
2063
|
+
})
|
|
2064
|
+
|
|
2065
|
+
it('should read storage slot with hex string slot', async () => {
|
|
2066
|
+
// Set storage at a specific slot using anvil_setStorageAt
|
|
2067
|
+
await anvilProvider.send('anvil_setStorageAt', [testAddress, testSlot, expectedStorageValue])
|
|
2068
|
+
|
|
2069
|
+
const value: GetStorageAtValue = {
|
|
2070
|
+
type: 'get-storage-at',
|
|
2071
|
+
arguments: {
|
|
2072
|
+
address: testAddress,
|
|
2073
|
+
slot: testSlot
|
|
2074
|
+
},
|
|
2075
|
+
}
|
|
2076
|
+
const result = await resolver.resolve(value, context)
|
|
2077
|
+
expect(result).toBe(expectedStorageValue)
|
|
2078
|
+
})
|
|
2079
|
+
|
|
2080
|
+
it('should read storage slot with numeric slot', async () => {
|
|
2081
|
+
// Set storage at slot 0
|
|
2082
|
+
await anvilProvider.send('anvil_setStorageAt', [testAddress, testSlot, expectedStorageValue])
|
|
2083
|
+
|
|
2084
|
+
const value: GetStorageAtValue = {
|
|
2085
|
+
type: 'get-storage-at',
|
|
2086
|
+
arguments: {
|
|
2087
|
+
address: testAddress,
|
|
2088
|
+
slot: testSlotNumber
|
|
2089
|
+
},
|
|
2090
|
+
}
|
|
2091
|
+
const result = await resolver.resolve(value, context)
|
|
2092
|
+
expect(result).toBe(expectedStorageValue)
|
|
2093
|
+
})
|
|
2094
|
+
|
|
2095
|
+
it('should read storage slot with string number slot', async () => {
|
|
2096
|
+
// Set storage at slot 0
|
|
2097
|
+
await anvilProvider.send('anvil_setStorageAt', [testAddress, testSlot, expectedStorageValue])
|
|
2098
|
+
|
|
2099
|
+
const value: GetStorageAtValue = {
|
|
2100
|
+
type: 'get-storage-at',
|
|
2101
|
+
arguments: {
|
|
2102
|
+
address: testAddress,
|
|
2103
|
+
slot: '0'
|
|
2104
|
+
},
|
|
2105
|
+
}
|
|
2106
|
+
const result = await resolver.resolve(value, context)
|
|
2107
|
+
expect(result).toBe(expectedStorageValue)
|
|
2108
|
+
})
|
|
2109
|
+
|
|
2110
|
+
it('should read different storage slots', async () => {
|
|
2111
|
+
const slot1 = '0x0000000000000000000000000000000000000000000000000000000000000000'
|
|
2112
|
+
const slot2 = '0x0000000000000000000000000000000000000000000000000000000000000001'
|
|
2113
|
+
const value1 = '0x00000000000000000000000000000000000000000000000000000000000000aa'
|
|
2114
|
+
const value2 = '0x00000000000000000000000000000000000000000000000000000000000000bb'
|
|
2115
|
+
|
|
2116
|
+
await anvilProvider.send('anvil_setStorageAt', [testAddress, slot1, value1])
|
|
2117
|
+
await anvilProvider.send('anvil_setStorageAt', [testAddress, slot2, value2])
|
|
2118
|
+
|
|
2119
|
+
const valueForSlot1: GetStorageAtValue = {
|
|
2120
|
+
type: 'get-storage-at',
|
|
2121
|
+
arguments: {
|
|
2122
|
+
address: testAddress,
|
|
2123
|
+
slot: slot1
|
|
2124
|
+
},
|
|
2125
|
+
}
|
|
2126
|
+
const valueForSlot2: GetStorageAtValue = {
|
|
2127
|
+
type: 'get-storage-at',
|
|
2128
|
+
arguments: {
|
|
2129
|
+
address: testAddress,
|
|
2130
|
+
slot: slot2
|
|
2131
|
+
},
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
const result1 = await resolver.resolve(valueForSlot1, context)
|
|
2135
|
+
const result2 = await resolver.resolve(valueForSlot2, context)
|
|
2136
|
+
|
|
2137
|
+
expect(result1).toBe(value1)
|
|
2138
|
+
expect(result2).toBe(value2)
|
|
2139
|
+
})
|
|
2140
|
+
|
|
2141
|
+
it('should resolve address from context variable', async () => {
|
|
2142
|
+
context.setOutput('myAddress', testAddress)
|
|
2143
|
+
await anvilProvider.send('anvil_setStorageAt', [testAddress, testSlot, expectedStorageValue])
|
|
2144
|
+
|
|
2145
|
+
const value: GetStorageAtValue = {
|
|
2146
|
+
type: 'get-storage-at',
|
|
2147
|
+
arguments: {
|
|
2148
|
+
address: '{{myAddress}}',
|
|
2149
|
+
slot: testSlot
|
|
2150
|
+
},
|
|
2151
|
+
}
|
|
2152
|
+
const result = await resolver.resolve(value, context)
|
|
2153
|
+
expect(result).toBe(expectedStorageValue)
|
|
2154
|
+
})
|
|
2155
|
+
|
|
2156
|
+
it('should resolve slot from context variable', async () => {
|
|
2157
|
+
context.setOutput('mySlot', testSlot)
|
|
2158
|
+
await anvilProvider.send('anvil_setStorageAt', [testAddress, testSlot, expectedStorageValue])
|
|
2159
|
+
|
|
2160
|
+
const value: GetStorageAtValue = {
|
|
2161
|
+
type: 'get-storage-at',
|
|
2162
|
+
arguments: {
|
|
2163
|
+
address: testAddress,
|
|
2164
|
+
slot: '{{mySlot}}'
|
|
2165
|
+
},
|
|
2166
|
+
}
|
|
2167
|
+
const result = await resolver.resolve(value, context)
|
|
2168
|
+
expect(result).toBe(expectedStorageValue)
|
|
2169
|
+
})
|
|
2170
|
+
|
|
2171
|
+
it('should return zero storage for non-existent slot', async () => {
|
|
2172
|
+
const nonExistentSlot = '0x000000000000000000000000000000000000000000000000000000000000ffff'
|
|
2173
|
+
|
|
2174
|
+
const value: GetStorageAtValue = {
|
|
2175
|
+
type: 'get-storage-at',
|
|
2176
|
+
arguments: {
|
|
2177
|
+
address: testAddress,
|
|
2178
|
+
slot: nonExistentSlot
|
|
2179
|
+
},
|
|
2180
|
+
}
|
|
2181
|
+
const result = await resolver.resolve(value, context)
|
|
2182
|
+
// Non-existent slots return 0x00...00
|
|
2183
|
+
expect(result).toBe('0x0000000000000000000000000000000000000000000000000000000000000000')
|
|
2184
|
+
})
|
|
2185
|
+
|
|
2186
|
+
it('should throw error for invalid address', async () => {
|
|
2187
|
+
const value: GetStorageAtValue = {
|
|
2188
|
+
type: 'get-storage-at',
|
|
2189
|
+
arguments: {
|
|
2190
|
+
address: 'invalid-address',
|
|
2191
|
+
slot: testSlot
|
|
2192
|
+
},
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('Invalid address: invalid-address')
|
|
2196
|
+
})
|
|
2197
|
+
|
|
2198
|
+
it('should throw error for null address', async () => {
|
|
2199
|
+
const value: GetStorageAtValue = {
|
|
2200
|
+
type: 'get-storage-at',
|
|
2201
|
+
arguments: {
|
|
2202
|
+
address: null as any,
|
|
2203
|
+
slot: testSlot
|
|
2204
|
+
},
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('Invalid address: null')
|
|
2208
|
+
})
|
|
2209
|
+
|
|
2210
|
+
it('should throw error for undefined address', async () => {
|
|
2211
|
+
const value: GetStorageAtValue = {
|
|
2212
|
+
type: 'get-storage-at',
|
|
2213
|
+
arguments: {
|
|
2214
|
+
address: undefined as any,
|
|
2215
|
+
slot: testSlot
|
|
2216
|
+
},
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('Invalid address: undefined')
|
|
2220
|
+
})
|
|
2221
|
+
|
|
2222
|
+
it('should handle checksummed addresses', async () => {
|
|
2223
|
+
const checksummedAddress = '0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed'
|
|
2224
|
+
await anvilProvider.send('anvil_setStorageAt', [checksummedAddress, testSlot, expectedStorageValue])
|
|
2225
|
+
|
|
2226
|
+
const value: GetStorageAtValue = {
|
|
2227
|
+
type: 'get-storage-at',
|
|
2228
|
+
arguments: {
|
|
2229
|
+
address: checksummedAddress,
|
|
2230
|
+
slot: testSlot
|
|
2231
|
+
},
|
|
2232
|
+
}
|
|
2233
|
+
const result = await resolver.resolve(value, context)
|
|
2234
|
+
expect(result).toBe(expectedStorageValue)
|
|
2235
|
+
})
|
|
2236
|
+
|
|
2237
|
+
it('should handle lowercase addresses', async () => {
|
|
2238
|
+
const lowercaseAddress = testAddress.toLowerCase()
|
|
2239
|
+
await anvilProvider.send('anvil_setStorageAt', [lowercaseAddress, testSlot, expectedStorageValue])
|
|
2240
|
+
|
|
2241
|
+
const value: GetStorageAtValue = {
|
|
2242
|
+
type: 'get-storage-at',
|
|
2243
|
+
arguments: {
|
|
2244
|
+
address: lowercaseAddress,
|
|
2245
|
+
slot: testSlot
|
|
2246
|
+
},
|
|
2247
|
+
}
|
|
2248
|
+
const result = await resolver.resolve(value, context)
|
|
2249
|
+
expect(result).toBe(expectedStorageValue)
|
|
2250
|
+
})
|
|
2251
|
+
|
|
2252
|
+
it('should handle large slot numbers', async () => {
|
|
2253
|
+
// EIP-1967 implementation slot
|
|
2254
|
+
const eip1967Slot = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'
|
|
2255
|
+
await anvilProvider.send('anvil_setStorageAt', [testAddress, eip1967Slot, expectedStorageValue])
|
|
2256
|
+
|
|
2257
|
+
const value: GetStorageAtValue = {
|
|
2258
|
+
type: 'get-storage-at',
|
|
2259
|
+
arguments: {
|
|
2260
|
+
address: testAddress,
|
|
2261
|
+
slot: eip1967Slot
|
|
2262
|
+
},
|
|
2263
|
+
}
|
|
2264
|
+
const result = await resolver.resolve(value, context)
|
|
2265
|
+
expect(result).toBe(expectedStorageValue)
|
|
2266
|
+
})
|
|
2267
|
+
})
|
|
2268
|
+
|
|
2269
|
+
describe('compute-slot', () => {
|
|
2270
|
+
describe('mapping', () => {
|
|
2271
|
+
it('should compute a mapping value slot with an address key', async () => {
|
|
2272
|
+
const value: ComputeSlotValue = {
|
|
2273
|
+
type: 'compute-slot',
|
|
2274
|
+
arguments: {
|
|
2275
|
+
kind: 'mapping',
|
|
2276
|
+
slot: 2,
|
|
2277
|
+
key: '0x1111111111111111111111111111111111111111',
|
|
2278
|
+
keyType: 'address',
|
|
2279
|
+
},
|
|
2280
|
+
}
|
|
2281
|
+
const result = await resolver.resolve(value, context)
|
|
2282
|
+
expect(result).toBe('0x06bb1b9bc4293ba066a12274418b7ea4df183c2e4e6b39591987369520ca3956')
|
|
2283
|
+
})
|
|
2284
|
+
|
|
2285
|
+
it('should default keyType to uint256', async () => {
|
|
2286
|
+
const value: ComputeSlotValue = {
|
|
2287
|
+
type: 'compute-slot',
|
|
2288
|
+
arguments: {
|
|
2289
|
+
kind: 'mapping',
|
|
2290
|
+
slot: 1,
|
|
2291
|
+
key: 5,
|
|
2292
|
+
},
|
|
2293
|
+
}
|
|
2294
|
+
const result = await resolver.resolve(value, context)
|
|
2295
|
+
expect(result).toBe('0xe2689cd4a84e23ad2f564004f1c9013e9589d260bde6380aba3ca7e09e4df40c')
|
|
2296
|
+
})
|
|
2297
|
+
|
|
2298
|
+
it('should pack dynamic (string) keys', async () => {
|
|
2299
|
+
const value: ComputeSlotValue = {
|
|
2300
|
+
type: 'compute-slot',
|
|
2301
|
+
arguments: {
|
|
2302
|
+
kind: 'mapping',
|
|
2303
|
+
slot: 7,
|
|
2304
|
+
key: 'hello',
|
|
2305
|
+
keyType: 'string',
|
|
2306
|
+
},
|
|
2307
|
+
}
|
|
2308
|
+
const result = await resolver.resolve(value, context)
|
|
2309
|
+
expect(result).toBe('0xa39e328cf6237afe41b514c6c18ccdc6b503f43ce841d4b5bca5e763723b44a9')
|
|
2310
|
+
})
|
|
2311
|
+
|
|
2312
|
+
it('should support nesting for nested mappings', async () => {
|
|
2313
|
+
// balances[a][b] where the outer mapping is at slot 0
|
|
2314
|
+
const value: ComputeSlotValue = {
|
|
2315
|
+
type: 'compute-slot',
|
|
2316
|
+
arguments: {
|
|
2317
|
+
kind: 'mapping',
|
|
2318
|
+
key: '0x2222222222222222222222222222222222222222',
|
|
2319
|
+
keyType: 'address',
|
|
2320
|
+
slot: {
|
|
2321
|
+
type: 'compute-slot',
|
|
2322
|
+
arguments: {
|
|
2323
|
+
kind: 'mapping',
|
|
2324
|
+
slot: 0,
|
|
2325
|
+
key: '0x1111111111111111111111111111111111111111',
|
|
2326
|
+
keyType: 'address',
|
|
2327
|
+
},
|
|
2328
|
+
},
|
|
2329
|
+
},
|
|
2330
|
+
}
|
|
2331
|
+
const result = await resolver.resolve(value, context)
|
|
2332
|
+
expect(result).toBe('0xd1360c905f62126f789549525bc3219e0fb5feba645fd0705768a3cc3bf3fc5c')
|
|
2333
|
+
})
|
|
2334
|
+
|
|
2335
|
+
it('should resolve references in arguments', async () => {
|
|
2336
|
+
context.setOutput('mapSlot', 2)
|
|
2337
|
+
context.setOutput('owner', '0x1111111111111111111111111111111111111111')
|
|
2338
|
+
const value: ComputeSlotValue = {
|
|
2339
|
+
type: 'compute-slot',
|
|
2340
|
+
arguments: {
|
|
2341
|
+
kind: 'mapping',
|
|
2342
|
+
slot: '{{mapSlot}}',
|
|
2343
|
+
key: '{{owner}}',
|
|
2344
|
+
keyType: 'address',
|
|
2345
|
+
},
|
|
2346
|
+
}
|
|
2347
|
+
const result = await resolver.resolve(value, context)
|
|
2348
|
+
expect(result).toBe('0x06bb1b9bc4293ba066a12274418b7ea4df183c2e4e6b39591987369520ca3956')
|
|
2349
|
+
})
|
|
2350
|
+
|
|
2351
|
+
it('should throw when key is missing', async () => {
|
|
2352
|
+
const value = {
|
|
2353
|
+
type: 'compute-slot',
|
|
2354
|
+
arguments: { kind: 'mapping', slot: 0 },
|
|
2355
|
+
} as unknown as ComputeSlotValue
|
|
2356
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('compute-slot (mapping): "key" is required')
|
|
2357
|
+
})
|
|
2358
|
+
})
|
|
2359
|
+
|
|
2360
|
+
describe('dynamic-array', () => {
|
|
2361
|
+
it('should compute an element slot', async () => {
|
|
2362
|
+
const value: ComputeSlotValue = {
|
|
2363
|
+
type: 'compute-slot',
|
|
2364
|
+
arguments: { kind: 'dynamic-array', slot: 3, index: 4 },
|
|
2365
|
+
}
|
|
2366
|
+
const result = await resolver.resolve(value, context)
|
|
2367
|
+
expect(result).toBe('0xc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85f')
|
|
2368
|
+
})
|
|
2369
|
+
|
|
2370
|
+
it('should default index to 0', async () => {
|
|
2371
|
+
const value: ComputeSlotValue = {
|
|
2372
|
+
type: 'compute-slot',
|
|
2373
|
+
arguments: { kind: 'dynamic-array', slot: 3 },
|
|
2374
|
+
}
|
|
2375
|
+
const result = await resolver.resolve(value, context)
|
|
2376
|
+
// keccak256(slot) with index 0
|
|
2377
|
+
expect(result).toBe(ethers.keccak256(ethers.toBeHex(3, 32)))
|
|
2378
|
+
})
|
|
2379
|
+
|
|
2380
|
+
it('should account for multi-slot elements via elementSize', async () => {
|
|
2381
|
+
const value: ComputeSlotValue = {
|
|
2382
|
+
type: 'compute-slot',
|
|
2383
|
+
arguments: { kind: 'dynamic-array', slot: 3, index: 1, elementSize: 2 },
|
|
2384
|
+
}
|
|
2385
|
+
const result = await resolver.resolve(value, context)
|
|
2386
|
+
expect(result).toBe('0xc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85d')
|
|
2387
|
+
})
|
|
2388
|
+
})
|
|
2389
|
+
|
|
2390
|
+
describe('struct-field', () => {
|
|
2391
|
+
it('should add the field offset to the base slot', async () => {
|
|
2392
|
+
const value: ComputeSlotValue = {
|
|
2393
|
+
type: 'compute-slot',
|
|
2394
|
+
arguments: { kind: 'struct-field', slot: 100, offset: 3 },
|
|
2395
|
+
}
|
|
2396
|
+
const result = await resolver.resolve(value, context)
|
|
2397
|
+
expect(result).toBe('0x0000000000000000000000000000000000000000000000000000000000000067')
|
|
2398
|
+
})
|
|
2399
|
+
|
|
2400
|
+
it('should throw when offset is missing', async () => {
|
|
2401
|
+
const value = {
|
|
2402
|
+
type: 'compute-slot',
|
|
2403
|
+
arguments: { kind: 'struct-field', slot: 1 },
|
|
2404
|
+
} as unknown as ComputeSlotValue
|
|
2405
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('compute-slot (struct-field): "offset" is required')
|
|
2406
|
+
})
|
|
2407
|
+
})
|
|
2408
|
+
|
|
2409
|
+
describe('erc7201', () => {
|
|
2410
|
+
it('should compute the namespaced root for the EIP-7201 example', async () => {
|
|
2411
|
+
const value: ComputeSlotValue = {
|
|
2412
|
+
type: 'compute-slot',
|
|
2413
|
+
arguments: { kind: 'erc7201', id: 'example.main' },
|
|
2414
|
+
}
|
|
2415
|
+
const result = await resolver.resolve(value, context)
|
|
2416
|
+
expect(result).toBe('0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500')
|
|
2417
|
+
})
|
|
2418
|
+
|
|
2419
|
+
it('should match OpenZeppelin Ownable namespace', async () => {
|
|
2420
|
+
const value: ComputeSlotValue = {
|
|
2421
|
+
type: 'compute-slot',
|
|
2422
|
+
arguments: { kind: 'erc7201', id: 'openzeppelin.storage.Ownable' },
|
|
2423
|
+
}
|
|
2424
|
+
const result = await resolver.resolve(value, context)
|
|
2425
|
+
expect(result).toBe('0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300')
|
|
2426
|
+
})
|
|
2427
|
+
|
|
2428
|
+
it('should throw on empty id', async () => {
|
|
2429
|
+
const value = {
|
|
2430
|
+
type: 'compute-slot',
|
|
2431
|
+
arguments: { kind: 'erc7201', id: '' },
|
|
2432
|
+
} as unknown as ComputeSlotValue
|
|
2433
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('compute-slot (erc7201): "id" must be a non-empty string')
|
|
2434
|
+
})
|
|
2435
|
+
})
|
|
2436
|
+
|
|
2437
|
+
describe('eip1967', () => {
|
|
2438
|
+
it('should compute the implementation slot', async () => {
|
|
2439
|
+
const value: ComputeSlotValue = {
|
|
2440
|
+
type: 'compute-slot',
|
|
2441
|
+
arguments: { kind: 'eip1967', name: 'implementation' },
|
|
2442
|
+
}
|
|
2443
|
+
const result = await resolver.resolve(value, context)
|
|
2444
|
+
expect(result).toBe('0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc')
|
|
2445
|
+
})
|
|
2446
|
+
|
|
2447
|
+
it('should compute the admin slot', async () => {
|
|
2448
|
+
const value: ComputeSlotValue = {
|
|
2449
|
+
type: 'compute-slot',
|
|
2450
|
+
arguments: { kind: 'eip1967', name: 'admin' },
|
|
2451
|
+
}
|
|
2452
|
+
const result = await resolver.resolve(value, context)
|
|
2453
|
+
expect(result).toBe('0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103')
|
|
2454
|
+
})
|
|
2455
|
+
|
|
2456
|
+
it('should compute the beacon slot', async () => {
|
|
2457
|
+
const value: ComputeSlotValue = {
|
|
2458
|
+
type: 'compute-slot',
|
|
2459
|
+
arguments: { kind: 'eip1967', name: 'beacon' },
|
|
2460
|
+
}
|
|
2461
|
+
const result = await resolver.resolve(value, context)
|
|
2462
|
+
expect(result).toBe('0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50')
|
|
2463
|
+
})
|
|
2464
|
+
|
|
2465
|
+
it('should throw on unknown name', async () => {
|
|
2466
|
+
const value = {
|
|
2467
|
+
type: 'compute-slot',
|
|
2468
|
+
arguments: { kind: 'eip1967', name: 'nope' },
|
|
2469
|
+
} as unknown as ComputeSlotValue
|
|
2470
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('compute-slot (eip1967): "name" must be one of')
|
|
2471
|
+
})
|
|
2472
|
+
})
|
|
2473
|
+
|
|
2474
|
+
it('should chain into get-storage-at as the slot argument', async () => {
|
|
2475
|
+
// The computed slot is a valid bytes32 hex that get-storage-at can consume.
|
|
2476
|
+
const computed = await resolver.resolve<string>({
|
|
2477
|
+
type: 'compute-slot',
|
|
2478
|
+
arguments: { kind: 'eip1967', name: 'implementation' },
|
|
2479
|
+
} as ComputeSlotValue, context)
|
|
2480
|
+
expect(computed).toBe('0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc')
|
|
2481
|
+
})
|
|
2482
|
+
|
|
2483
|
+
it('should throw when kind is missing', async () => {
|
|
2484
|
+
const value = { type: 'compute-slot', arguments: {} } as unknown as ComputeSlotValue
|
|
2485
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('compute-slot: "kind" is required')
|
|
2486
|
+
})
|
|
2487
|
+
|
|
2488
|
+
it('should throw on unknown kind', async () => {
|
|
2489
|
+
const value = {
|
|
2490
|
+
type: 'compute-slot',
|
|
2491
|
+
arguments: { kind: 'bogus' },
|
|
2492
|
+
} as unknown as ComputeSlotValue
|
|
2493
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('compute-slot: unknown kind "bogus"')
|
|
2494
|
+
})
|
|
2495
|
+
})
|
|
2053
2496
|
})
|
package/src/lib/core/engine.ts
CHANGED
|
@@ -987,6 +987,58 @@ export class ExecutionEngine {
|
|
|
987
987
|
}
|
|
988
988
|
break
|
|
989
989
|
}
|
|
990
|
+
case 'assert': {
|
|
991
|
+
// Determine the source of ACTUAL value
|
|
992
|
+
let actual: any
|
|
993
|
+
let describe: string
|
|
994
|
+
if (action.arguments.to !== undefined) {
|
|
995
|
+
// Use eth_call via the call resolver
|
|
996
|
+
actual = await this.resolver.resolve(
|
|
997
|
+
{ type: 'call', arguments: { to: action.arguments.to, signature: action.arguments.signature, values: action.arguments.values ?? [] } },
|
|
998
|
+
context,
|
|
999
|
+
scope,
|
|
1000
|
+
)
|
|
1001
|
+
describe = action.arguments.signature ?? 'call'
|
|
1002
|
+
} else {
|
|
1003
|
+
// Use the `actual` value resolver
|
|
1004
|
+
actual = await this.resolver.resolve(action.arguments.actual, context, scope)
|
|
1005
|
+
describe = action.arguments.signature ?? 'value'
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Determine the comparator key and expected value (exactly one required)
|
|
1009
|
+
const comparatorKeys = ['eq', 'neq', 'gt', 'lt', 'gte', 'lte']
|
|
1010
|
+
const args = action.arguments as any
|
|
1011
|
+
const presentKeys = comparatorKeys.filter((key) => args[key] !== undefined)
|
|
1012
|
+
if (presentKeys.length === 0) {
|
|
1013
|
+
throw new Error(`Action "${actionName}": assert must have exactly one of: ${comparatorKeys.join(', ')}`)
|
|
1014
|
+
}
|
|
1015
|
+
if (presentKeys.length > 1) {
|
|
1016
|
+
throw new Error(`Action "${actionName}": assert must have exactly one comparator, but got: ${presentKeys.join(', ')}`)
|
|
1017
|
+
}
|
|
1018
|
+
const operation = presentKeys[0]
|
|
1019
|
+
let expected: any = args[operation]
|
|
1020
|
+
|
|
1021
|
+
// Resolve expected value
|
|
1022
|
+
expected = await this.resolver.resolve(expected, context, scope)
|
|
1023
|
+
|
|
1024
|
+
// Compare using basic-arithmetic (which returns a boolean for eq/neq/gt/lt/gte/lte)
|
|
1025
|
+
const ok = await this.resolver.resolve(
|
|
1026
|
+
{ type: 'basic-arithmetic', arguments: { operation, values: [actual, expected] } },
|
|
1027
|
+
context,
|
|
1028
|
+
scope,
|
|
1029
|
+
)
|
|
1030
|
+
|
|
1031
|
+
if (!ok) {
|
|
1032
|
+
const messagePart = action.arguments.message ? `: ${action.arguments.message}` : ''
|
|
1033
|
+
throw new Error(`assert failed${messagePart}: ${describe} (actual=${actual}, expected=${expected}, op=${operation})`)
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// Store output if named and no custom output
|
|
1037
|
+
if (action.name && !hasCustomOutput) {
|
|
1038
|
+
context.setOutput(`${action.name}.actual`, actual)
|
|
1039
|
+
}
|
|
1040
|
+
break
|
|
1041
|
+
}
|
|
990
1042
|
default:
|
|
991
1043
|
throw new Error(`Unknown or unimplemented primitive action type: ${(action as any).type}`)
|
|
992
1044
|
}
|
package/src/lib/core/loader.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { parseConstants } from '../parsers/constants'
|
|
|
7
7
|
|
|
8
8
|
export interface ProjectLoaderOptions {
|
|
9
9
|
loadStdTemplates?: boolean
|
|
10
|
+
loadContracts?: boolean
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export class ProjectLoader {
|
|
@@ -28,7 +29,9 @@ export class ProjectLoader {
|
|
|
28
29
|
|
|
29
30
|
async load() {
|
|
30
31
|
// Load all contracts from the project root first
|
|
31
|
-
|
|
32
|
+
if (this.options.loadContracts !== false) {
|
|
33
|
+
await this.contractRepository.loadFrom(this.projectRoot)
|
|
34
|
+
}
|
|
32
35
|
|
|
33
36
|
// Load standard library templates (unless disabled)
|
|
34
37
|
if (this.options.loadStdTemplates !== false) {
|
|
@@ -119,6 +122,9 @@ export class ProjectLoader {
|
|
|
119
122
|
this.templates.set(template.name, template)
|
|
120
123
|
continue
|
|
121
124
|
}
|
|
125
|
+
if (raw && typeof raw === 'object' && (raw.type === 'source' || raw.type === 'constants')) {
|
|
126
|
+
continue
|
|
127
|
+
}
|
|
122
128
|
|
|
123
129
|
const job = parseJob(content)
|
|
124
130
|
job._path = filePath
|
|
@@ -244,4 +250,4 @@ export class ProjectLoader {
|
|
|
244
250
|
return false
|
|
245
251
|
}
|
|
246
252
|
}
|
|
247
|
-
}
|
|
253
|
+
}
|
package/src/lib/core/resolver.ts
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
ComputeCreateValue,
|
|
9
9
|
ComputeCreate2Value,
|
|
10
10
|
ReadBalanceValue,
|
|
11
|
+
GetStorageAtValue,
|
|
12
|
+
ComputeSlotValue,
|
|
11
13
|
BasicArithmeticValue,
|
|
12
14
|
CallValue,
|
|
13
15
|
ContractExistsValue,
|
|
@@ -197,6 +199,10 @@ export class ValueResolver {
|
|
|
197
199
|
return this.resolveComputeCreate2(resolvedArgs as ComputeCreate2Value['arguments'])
|
|
198
200
|
case 'read-balance':
|
|
199
201
|
return this.resolveReadBalance(resolvedArgs as ReadBalanceValue['arguments'], context)
|
|
202
|
+
case 'get-storage-at':
|
|
203
|
+
return this.resolveGetStorageAt(resolvedArgs as GetStorageAtValue['arguments'], context)
|
|
204
|
+
case 'compute-slot':
|
|
205
|
+
return this.resolveComputeSlot(resolvedArgs as ComputeSlotValue['arguments'])
|
|
200
206
|
case 'basic-arithmetic':
|
|
201
207
|
return this.resolveBasicArithmetic(resolvedArgs as BasicArithmeticValue['arguments'])
|
|
202
208
|
case 'call':
|
|
@@ -375,6 +381,116 @@ export class ValueResolver {
|
|
|
375
381
|
return balance.toString()
|
|
376
382
|
}
|
|
377
383
|
|
|
384
|
+
private async resolveGetStorageAt(args: GetStorageAtValue['arguments'], context: ExecutionContext): Promise<string> {
|
|
385
|
+
const { address, slot } = args
|
|
386
|
+
|
|
387
|
+
// Check if the address is a valid address
|
|
388
|
+
if (!isAddress(address)) {
|
|
389
|
+
throw new Error(`Invalid address: ${address}`)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Normalize the slot to a BigInt
|
|
393
|
+
// After resolution, slot should be a string or number
|
|
394
|
+
const slotValue = ethers.toBigInt(slot as string | number)
|
|
395
|
+
|
|
396
|
+
const storageValue = await context.provider.getStorage(address, slotValue)
|
|
397
|
+
// getStorage returns a hex string, ensure it's 32 bytes (64 hex chars + 0x)
|
|
398
|
+
return ethers.hexlify(storageValue)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Computes EVM storage slots for the common Solidity storage layouts.
|
|
403
|
+
* Always returns a 32-byte, 0x-prefixed lowercase hex string so the result can
|
|
404
|
+
* be fed straight into `get-storage-at` or nested as the `slot` of another
|
|
405
|
+
* `compute-slot` (e.g. nested mappings, structs inside mappings, ...).
|
|
406
|
+
*/
|
|
407
|
+
private resolveComputeSlot(args: ComputeSlotValue['arguments']): string {
|
|
408
|
+
if (!args || typeof args !== 'object' || typeof (args as any).kind !== 'string') {
|
|
409
|
+
throw new Error('compute-slot: "kind" is required')
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
switch (args.kind) {
|
|
413
|
+
case 'mapping': {
|
|
414
|
+
const { slot, key, keyType } = args
|
|
415
|
+
if (slot === undefined || slot === null) {
|
|
416
|
+
throw new Error('compute-slot (mapping): "slot" is required')
|
|
417
|
+
}
|
|
418
|
+
if (key === undefined || key === null) {
|
|
419
|
+
throw new Error('compute-slot (mapping): "key" is required')
|
|
420
|
+
}
|
|
421
|
+
const baseSlot = ethers.toBigInt(slot as string | number)
|
|
422
|
+
const type = (keyType as string) || 'uint256'
|
|
423
|
+
try {
|
|
424
|
+
// mapping value slot = keccak256(h(key) . p)
|
|
425
|
+
// Dynamic key types (string/bytes) are packed; value types are ABI-encoded (left-padded to 32 bytes).
|
|
426
|
+
if (type === 'string' || type === 'bytes') {
|
|
427
|
+
return ethers.solidityPackedKeccak256([type, 'uint256'], [key, baseSlot])
|
|
428
|
+
}
|
|
429
|
+
const encoded = ethers.AbiCoder.defaultAbiCoder().encode([type, 'uint256'], [key, baseSlot])
|
|
430
|
+
return ethers.keccak256(encoded)
|
|
431
|
+
} catch (error) {
|
|
432
|
+
throw new Error(`compute-slot (mapping): failed to encode key as "${type}": ${error instanceof Error ? error.message : String(error)}`)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
case 'dynamic-array': {
|
|
437
|
+
const { slot, index, elementSize } = args
|
|
438
|
+
if (slot === undefined || slot === null) {
|
|
439
|
+
throw new Error('compute-slot (dynamic-array): "slot" is required')
|
|
440
|
+
}
|
|
441
|
+
const baseSlot = ethers.toBigInt(slot as string | number)
|
|
442
|
+
const idx = index === undefined || index === null ? 0n : ethers.toBigInt(index as string | number)
|
|
443
|
+
const size = elementSize === undefined || elementSize === null ? 1n : ethers.toBigInt(elementSize as string | number)
|
|
444
|
+
// element slot = keccak256(slot) + index * elementSize
|
|
445
|
+
const dataStart = ethers.toBigInt(ethers.keccak256(ethers.toBeHex(baseSlot, 32)))
|
|
446
|
+
return ethers.toBeHex(dataStart + idx * size, 32)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
case 'struct-field': {
|
|
450
|
+
const { slot, offset } = args
|
|
451
|
+
if (slot === undefined || slot === null) {
|
|
452
|
+
throw new Error('compute-slot (struct-field): "slot" is required')
|
|
453
|
+
}
|
|
454
|
+
if (offset === undefined || offset === null) {
|
|
455
|
+
throw new Error('compute-slot (struct-field): "offset" is required')
|
|
456
|
+
}
|
|
457
|
+
const result = ethers.toBigInt(slot as string | number) + ethers.toBigInt(offset as string | number)
|
|
458
|
+
return ethers.toBeHex(result, 32)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
case 'erc7201': {
|
|
462
|
+
const { id } = args
|
|
463
|
+
if (typeof id !== 'string' || id.length === 0) {
|
|
464
|
+
throw new Error('compute-slot (erc7201): "id" must be a non-empty string')
|
|
465
|
+
}
|
|
466
|
+
// keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))
|
|
467
|
+
const idHash = ethers.toBigInt(ethers.keccak256(ethers.toUtf8Bytes(id)))
|
|
468
|
+
const inner = ethers.keccak256(ethers.toBeHex(idHash - 1n, 32))
|
|
469
|
+
const mask = ((1n << 256n) - 1n) ^ 0xffn
|
|
470
|
+
return ethers.toBeHex(ethers.toBigInt(inner) & mask, 32)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
case 'eip1967': {
|
|
474
|
+
const labels: Record<string, string> = {
|
|
475
|
+
implementation: 'eip1967.proxy.implementation',
|
|
476
|
+
admin: 'eip1967.proxy.admin',
|
|
477
|
+
beacon: 'eip1967.proxy.beacon',
|
|
478
|
+
}
|
|
479
|
+
const name = args.name as string
|
|
480
|
+
const label = labels[name]
|
|
481
|
+
if (!label) {
|
|
482
|
+
throw new Error(`compute-slot (eip1967): "name" must be one of implementation, admin, beacon (got "${name}")`)
|
|
483
|
+
}
|
|
484
|
+
// slot = keccak256("eip1967.proxy.<name>") - 1
|
|
485
|
+
const slot = ethers.toBigInt(ethers.keccak256(ethers.toUtf8Bytes(label))) - 1n
|
|
486
|
+
return ethers.toBeHex(slot, 32)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
default:
|
|
490
|
+
throw new Error(`compute-slot: unknown kind "${(args as any).kind}"`)
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
378
494
|
private resolveBasicArithmetic(args: BasicArithmeticValue['arguments']): string | boolean {
|
|
379
495
|
if (!args.values || args.values.length < 2) {
|
|
380
496
|
throw new Error(`basic-arithmetic requires at least 2 values, got ${args.values?.length ?? 0}`)
|