0xweb
Version:
Contract package manager and other web3 tools
136 lines (106 loc) • 4.67 kB
text/typescript
import alot from 'alot';
import { AbiDeserializer } from '@dequanto/contracts/utils/AbiDeserializer';
import { $abiParser } from '@dequanto/utils/$abiParser';
import { $buffer } from '@dequanto/utils/$buffer';
import { $require } from '@dequanto/utils/$require';
import { SlotsStorageTransportForArray } from '../SlotsStorageTransport';
import { ASlotsStorageHandler } from '../SlotsStorageHandler';
import { IAccessorItem } from '../Accessor';
import { $contract } from '@dequanto/utils/$contract';
import { $abiUtils } from '@dequanto/utils/$abiUtils';
import { $bigint } from '@dequanto/utils/$bigint';
import { $hex } from '@dequanto/utils/$hex';
import { ISlotVarDefinition } from '@dequanto/solidity/SlotsParser/models';
export class SlotBytesHandler extends ASlotsStorageHandler {
static supports (slot: ISlotVarDefinition) {
return slot.type === 'bytes';
}
async get (keys?: IAccessorItem[]){
this.requireNoKeys(keys);
let { slot } = this;
let location = slot.slot;
let memory = [];
let slotValue = await this.transport.getStorageAt(location, 0, 256);
/**
* Per slot 32 bytes
* At original slot position, the last n-bytes is the SIZE of the STRING
*
* If a string < 31 bytes then it takes the first 31 bytes of the original slot
* otherwise take the SIZE of the STRING calculate how much slots it occupies and read dynamic bytes array
*/
let arraySizeMatch = /0x0{2,}(?<size>[^0][\da-f]+)$/i.exec(slotValue);
if (arraySizeMatch != null) {
let size = parseInt(arraySizeMatch.groups.size, 16) - 1;
memory = await this.readMultiSlot(size);
} else {
let length = parseInt(slotValue.substring(slotValue.length - 2), 16);
let raw = $hex.raw(slotValue);
slotValue = raw.substring(0, length);
memory = [ slotValue ];
}
memory = memory.map(hex => $buffer.fromHex(hex));
let value = $buffer.toHex($buffer.concat(memory));
return value;
}
async set(keys: IAccessorItem[], value: string) {
this.requireNoKeys(keys);
let { slot } = this;
let hex = $hex.toHexBuffer(value);
let length = hex.length - 2;
let bytesCount = length / 2;
if (bytesCount > 31) {
await this.writeMultiSlot(hex);
return;
}
hex = $hex.concat([
$hex.padBytes(hex, 31, { padEnd: true }),
$hex.toHexBuffer(length)
]);
await this.transport.setStorageAt(slot.slot, 0, 256, hex);
}
async fetchAll() {
return this.get();
}
protected async readMultiSlot (size: number) {
let SLOT_SIZE = 64;
let slotCount = Math.ceil(size / SLOT_SIZE);
$require.Number(slotCount, `Slots count is not a number for ${size}`);
$require.True(slotCount < 50, `The string is too big. Prevented to make ${slotCount} requests in ${this.slot.name} at slot ${this.slot.slot}`);
return await alot
.fromRange(0, slotCount)
.mapAsync(async i => {
let reader = new SlotsStorageTransportForArray(this.transport, this.slot.slot, i, 1);
let slotValue = await reader.getStorageAt(0);
return slotValue;
})
.toArrayAsync()
}
protected requireNoKeys (keys: IAccessorItem[]) {
if (keys?.length > 0) {
throw new Error(`ValueTypes cann't have the nested accessors: ${ keys.map(x => x.key).join('.') }`);
}
}
protected async writeMultiSlot (buffer: string) {
let hex = $hex.raw(buffer);
let SLOT_SIZE = 64;
let slotCount = Math.ceil(hex.length / SLOT_SIZE);
$require.True(slotCount < 50, `The string is too big. Prevented to make ${slotCount} requests`);
//> Write length
this.transport.setStorageAt(this.slot.slot, 0, 256, hex.length);
//> Write all slots
return await alot
.fromRange(0, slotCount)
.mapAsync(async i => {
let buf = '0x' + hex.substring(i * SLOT_SIZE, i * SLOT_SIZE + SLOT_SIZE);
let slotValue = await this.transport.setStorageAt(this.getSlot(i), 0, 256, buf);
return slotValue;
})
.toArrayAsync()
}
protected getSlot (idx: number) {
let slotArrNr = this.slot.slot;
let x = BigInt($contract.keccak256($abiUtils.encodePacked(['uint256'], [slotArrNr])));
let uint = x + BigInt(idx);
return $bigint.toHex(uint);
}
}