Inline AssemblyとStorageについて、コード書く時の参考をイメージしてまとめました。
https://github.com/eggdragons/how-to-use-foundry
Inline Assemblyを使ってStorageへのアクセスについて
先ほどのリポジトリの中のsrc/storage/StorageAssmebly.sol
が対象になります。
test/storage/StorageAssembly.t.sol
が上記のテストになっています。
このコントラクトでは、slot0にstorageAが、slot1にstorageBが、slot2にstorageCが格納されています。
uint256 public storageA = 1; // slot0
uint256 public storageB = 2; // slot1
uint256 public storageC = 3; // slot2
forge inspect StorageAssembly storage-layout --pretty
をターミナルで入力してもらうと、このコントラクトのストレージレイアウトを確認できるので、やってみてくださいね!
Assemblyを使用してstorageへアクセスするには、slot番号を使ってアクセスします。
function getSlotStorageA() public pure returns (uint256 _slot) {
assembly {
_slot := storageA.slot
}
}
このような方法で、slot番号を取得します。
storageへの読み書きは、次のような方法でやります。
function getStorageValue(uint256 slot) public view returns (uint256 value) {
assembly {
value := sload(slot)
}
}
function setStorageValue(uint256 slot, uint256 value) public {
assembly {
sstore(slot, value)
}
}
foundryでは、次のような方法でstorageにアクセスすることができます。
// find slot
function testGetSlotForgeStdStorage() public {
uint256 slot = stdstore.target(address(storageAssembly)).sig("storageA()").find();
assertEq(slot, storageAssembly.getSlotStorageA());
}
// read value
function testReadStorageValueForgeStdStorage() public {
uint256 value = stdstore.target(address(storageAssembly)).sig("storageA()").read_uint();
assertEq(value, storageAssembly.storageA());
}
// wtite value
function testWriteStorageValueForgeStdStorage(uint256 value) public {
stdstore.target(address(storageAssembly)).sig("storageA()").checked_write(value);
assertEq(value, storageAssembly.storageA());
}
Inline Assemblyを使ってPackingされたStorageへのアクセスについて
ここでは、src/storage/PackingStorageAssembly.sol
が対象になります。
test/storage/PackingStorageAssembly.t.sol
が上記のテストになっています。
このコントラクトでは、slot0にstorageD、storageE、storageFがパッキングされて格納されています。
uint128 public storageD = 10;
uint64 public storageE = 20;
uint64 public storageF = 30;
/*
| uint64 | uint64 | uint128 |
| storageF | storageE | storageD |
| 00...030 | 00...020 | 00...010 |
*/
forge inspect PackingStorageAssembly storage-layout --pretty
をターミナルで入力してもらうと、このコントラクトのストレージレイアウトを確認できるので、やってみてくださいね!
Assemblyを使用してstorageへアクセスするためには、前章の通りslot番号を使ってアクセスする必要があります。
しかし、今回のケースの場合、一つのslotにstorageD、storageE、storageFが格納されています。
そのためは、下記の結果は、すべて0になります。
function getSlotStorageD() public pure returns (uint256 _slot) {
assembly {
_slot := storageD.slot
}
}
function getSlotStorageE() public pure returns (uint256 _slot) {
assembly {
_slot := storageE.slot
}
}
function getSlotStorageF() public pure returns (uint256 _slot) {
assembly {
_slot := storageF.slot
}
}
そこで、各データへの読み書きを行う際は、自分でビットシフトをしたりパッキングする必要があります。
function getStorageValuePacking(uint256 slot, uint256 key) public view returns (uint256 value) {
assembly {
value := sload(slot)
switch key
case 0 {
let mask := sub(shl(128, 1), 1)
value := and(value, mask)
}
case 1 {
value := shr(128, value)
let mask := sub(shl(64, 1), 1)
value := and(value, mask)
}
case 2 {
value := shr(192, value)
let mask := sub(shl(64, 1), 1)
value := and(value, mask)
}
default {}
}
}
なお、上記コードは分かりやすい様に書いたコードですので、実際に使用する際は、下記のようなコードを使うことが多いです。
function getStorageValuePackingPractical(uint256 slot) public view returns (uint128 d, uint64 e, uint64 f) {
assembly {
let value := sload(slot)
d := value
e := shr(128, value)
f := shr(192, value)
}
}
なお、foundryでは、まだパッキングスロットへのアクセスはサポートされていません。
Inline Assemblyを使ってMappingされたStructStorageへのアクセスについて
ここでは、src/storage/StructStorageAssembly.sol
が対象になります。
test/storage/StuctStorageAssembly.t.sol
が上記のテストになっています。
このコントラクトでは、下記の様なmappingStructStorageに各データを格納するコントラクトになっています。
struct StructStorage {
uint128 storageH;
uint64 storageI;
uint64 storageJ;
uint64 storageK;
}
mapping(uint256 => StructStorage) public mappingStructStorages;
/*
Slot α + 0
| uint64 | uint64 | uint128 |
| storageJ | storageI | storageH |
| 00...030 | 00...020 | 00...010 |
Slot α + 1
| uint64 |
| storageK |
| 00...030 |
*/
Assemblyを使用して各データへアクセスする方法は今までの内容を複合させた感じになります。
Structの中も今まで同様パッキングされており、今回のケースだとSlotA(storageJ、storageI、storageHがパッキングされたもの)とSlotB(storageK)が含まれています。
それぞれ各Slotへアクセスするためには、StructのSlotがαだとすると、SlotAはα+0、SlotBはα+1になります。
// if you want to test index=0 you need to change the return type
function getMappingStructStorageAssembly(uint256 key, uint256 index) public view returns (uint64 result) {
uint128 h;
uint64 i;
uint64 j;
uint64 k;
assembly {
mstore(0x00, key)
mstore(0x20, mappingStructStorages.slot)
let slot := keccak256(0, 0x40)
let value := sload(slot)
h := and(value, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
i := shr(128, value)
j := shr(192, value)
value := sload(add(slot, 1))
k := value
switch index
case 1 { result := i }
case 2 { result := j }
case 3 { result := k }
default { result := 0 }
}
}
なお、上記コードは分かりやすい様に書いたコードですので、実際に使用する際は、下記のようなコードを使うことが多いです。
function getMappingStructStorageAssemblyOtherSlotMethod(uint256 key)
public
view
returns (uint128 h, uint64 i, uint64 j, uint64 k)
{
StructStorage storage mappingStructStorage = mappingStructStorages[key];
assembly {
let slot := mappingStructStorage.slot
let value := sload(slot)
h := and(value, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
i := shr(128, value)
j := shr(192, value)
value := sload(add(slot, 1))
k := value
}
}
なお、foundryでは、下記の様な方法でアクセスします。
// read value Mapping
function testReadMappingStorageValueForgeStdStorage(uint256 key, uint256 value) public {
// init
storageAssembly.setMappingStorage(key, value);
uint256 read_value =
stdstore.target(address(storageAssembly)).sig("mappingStorage(uint256)").with_key(key).read_uint();
assertEq(read_value, storageAssembly.getMappingStorage(key));
}
Inline Assemblyを使ってPackingされたStorageへのアクセスについて
ここでは、src/storage/PackingStorageAssembly.sol
が対象になります。
test/storage/PackingStorageAssembly.t.sol
が上記のテストになっています。
このコントラクトでは、slot0にstorageD、storageE、storageFがパッキングされて格納されています。
uint128 public storageD = 10;
uint64 public storageE = 20;
uint64 public storageF = 30;
/*
| uint64 | uint64 | uint128 |
| storageF | storageE | storageD |
| 00...030 | 00...020 | 00...010 |
*/
forge inspect PackingStorageAssembly storage-layout --pretty
をターミナルで入力してもらうと、このコントラクトのストレージレイアウトを確認できるので、やってみてくださいね!
Assemblyを使用してstorageへアクセスするためには、前章の通りslot番号を使ってアクセスする必要があります。
しかし、今回のケースの場合、一つのslotにstorageD、storageE、storageFが格納されています。
そのためは、下記の結果は、すべて0になります。
function getSlotStorageD() public pure returns (uint256 _slot) {
assembly {
_slot := storageD.slot
}
}
function getSlotStorageE() public pure returns (uint256 _slot) {
assembly {
_slot := storageE.slot
}
}
function getSlotStorageF() public pure returns (uint256 _slot) {
assembly {
_slot := storageF.slot
}
}
そこで、各データへの読み書きを行う際は、自分でビットシフトをしたりパッキングする必要があります。
function getStorageValuePacking(uint256 slot, uint256 key) public view returns (uint256 value) {
assembly {
value := sload(slot)
switch key
case 0 {
let mask := sub(shl(128, 1), 1)
value := and(value, mask)
}
case 1 {
value := shr(128, value)
let mask := sub(shl(64, 1), 1)
value := and(value, mask)
}
case 2 {
value := shr(192, value)
let mask := sub(shl(64, 1), 1)
value := and(value, mask)
}
default {}
}
}
なお、上記コードは分かりやすい様に書いたコードですので、実際に使用する際は、下記のようなコードを使うことが多いです。
function getStorageValuePackingPractical(uint256 slot) public view returns (uint128 d, uint64 e, uint64 f) {
assembly {
let value := sload(slot)
d := value
e := shr(128, value)
f := shr(192, value)
}
}
なお、foundryでは、まだパッキングスロットへのアクセスはサポートされていません。
一方で、パッキングされていないStructのSlotへはアクセスできます。
struct StructStorageFoundry {
uint256 storageX;
uint256 storageY;
}
mapping(uint256 => StructStorageFoundry) public mappingStructStoragesFoundry;
// read value Struct Mapping
function testReadMappingStorageValueForgeStdStorage(uint256 key, uint256 valueX, uint256 valueY) public {
// init
storageAssembly.setMappingStructStorageFoundry(key, valueX, valueY);
// X depth 0
uint256 slotX = stdstore.target(address(storageAssembly)).sig(
storageAssembly.mappingStructStoragesFoundry.selector
).with_key(key).depth(0).find();
uint256 x = uint256(vm.load(address(storageAssembly), bytes32(uint256(slotX))));
// Y depth 1
uint256 y = stdstore.target(address(storageAssembly)).sig(storageAssembly.mappingStructStoragesFoundry.selector)
.with_key(key).depth(1).read_uint();
assertEq(x, valueX);
assertEq(y, valueY);
}
今回は、Inline Assemblyを使用してさまざまなStorageへのアクセスをまとめてみました