-
Notifications
You must be signed in to change notification settings - Fork 0
/
storage.ts
167 lines (148 loc) · 5.34 KB
/
storage.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import { BigNumber, BigNumberish, providers } from "ethers";
import { ethers } from "hardhat";
import { AddressLike, toAddress } from "./types";
export async function getStorage(
contract: AddressLike,
slot: string | number,
fromProvider?: providers.JsonRpcProvider
): Promise<string> {
slot = getSlot(slot, true);
return await (fromProvider ?? ethers.provider).send("eth_getStorageAt", [toAddress(contract), slot]);
}
export async function getStorageAddress(
contract: AddressLike,
slot: string | number,
fromProvider?: providers.JsonRpcProvider
): Promise<string> {
let value = await getStorage(contract, slot, fromProvider);
value = ethers.utils.hexStripZeros(value);
value = ethers.utils.hexZeroPad(value, 20);
return ethers.utils.getAddress(value);
}
export async function getStorageNumber(contract: AddressLike, slot: string | number): Promise<BigNumber> {
const value = await getStorage(contract, slot);
return BigNumber.from(value);
}
async function getStoragePackedNumber(
contract: AddressLike,
slot: string | number,
offsetInBytes: number,
numberOfBytes: number
): Promise<BigNumber> {
offsetInBytes = 32 - offsetInBytes - numberOfBytes;
const slotHex = await getStorage(contract, slot);
const valueHex = "0x" + slotHex.substring(offsetInBytes * 2 + 2, offsetInBytes * 2 + numberOfBytes * 2 + 2);
return BigNumber.from(valueHex);
}
export async function getStoragePackedUint32(
contract: AddressLike,
slot: string | number,
offsetInBytes: number
): Promise<BigNumber> {
const value = await getStoragePackedNumber(contract, slot, offsetInBytes, 4);
return value;
}
export async function getStoragePackedBool(
contract: AddressLike,
slot: string | number,
offsetInBytes: number
): Promise<boolean> {
const value = await getStoragePackedUint8(contract, slot, offsetInBytes);
return value.eq(1);
}
export async function getStoragePackedUint8(
contract: AddressLike,
slot: string | number,
offsetInBytes: number
): Promise<BigNumber> {
const value = await getStoragePackedNumber(contract, slot, offsetInBytes, 1);
return value;
}
export async function getCode(fromContract: AddressLike, fromProvider?: providers.Provider): Promise<string> {
return await (fromProvider ?? ethers.provider).getCode(toAddress(fromContract));
}
export async function hasCode(contract: AddressLike | string): Promise<boolean> {
const address = typeof contract === "string" ? contract : contract.address;
const code = await ethers.provider.getCode(address);
return code !== "0x";
}
export async function setCodeFromContract(
toContract: AddressLike,
fromContract: AddressLike,
fromProvider?: providers.JsonRpcProvider,
include1967Proxy?: boolean
) {
const code = await getCode(fromContract, fromProvider);
await setCodeTo(toContract, code);
if (include1967Proxy) {
// from https://eips.ethereum.org/EIPS/eip-1967
const implementationStorageSlot = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
const proxyAddress = await getStorageAddress(fromContract, implementationStorageSlot, fromProvider);
if (proxyAddress != ethers.constants.AddressZero) {
const proxyCode = await getCode(proxyAddress, fromProvider);
await setCodeTo(proxyAddress, proxyCode);
await setStorage(toContract, implementationStorageSlot, proxyAddress);
}
}
}
export async function setCodeTo(contract: AddressLike, code: string) {
await ethers.provider.send("hardhat_setCode", [toAddress(contract), code]);
}
export async function setStorage(contract: AddressLike, slot: string | number, value: string | number) {
slot = getSlot(slot);
if (typeof value === "number") {
value = ethers.utils.hexValue(value);
}
value = ethers.utils.hexZeroPad(value, 32);
await ethers.provider.send("hardhat_setStorageAt", [toAddress(contract), slot, value]);
}
export async function setStoragePackedUint32(
contract: AddressLike,
slot: string | number,
offsetInBytes: number,
value: BigNumberish
) {
await setStoragePackedValue(contract, slot, offsetInBytes, value, 4);
}
export async function setStoragePackedBool(
contract: AddressLike,
slot: string | number,
offsetInBytes: number,
value: boolean
) {
const numberValue = value ? 1 : 0;
await setStoragePackedUint8(contract, slot, offsetInBytes, numberValue);
}
export async function setStoragePackedUint8(
contract: AddressLike,
slot: string | number,
offsetInBytes: number,
value: BigNumberish
) {
await setStoragePackedValue(contract, slot, offsetInBytes, value, 1);
}
async function setStoragePackedValue(
contract: AddressLike,
slot: string | number,
offsetInBytes: number,
newValue: BigNumberish,
numberOfBytes: number
) {
const hexValue = ethers.utils.hexZeroPad(ethers.utils.hexValue(newValue), numberOfBytes).substring(2);
let storage = await getStorage(contract, slot);
// Variables are packed lower order aligned, flipping the offset
offsetInBytes = 32 - offsetInBytes - hexValue.length / 2;
// Update existing storage at offset
storage =
storage.substring(0, offsetInBytes * 2 + 2) + hexValue + storage.substring(offsetInBytes * 2 + hexValue.length + 2);
await setStorage(contract, slot, storage);
}
function getSlot(slot: string | number, pad = false): string {
if (typeof slot === "number") {
slot = ethers.utils.hexValue(slot);
}
if (pad) {
slot = ethers.utils.hexZeroPad(slot, 32);
}
return slot;
}