public static Dictionary <Type, (Address address, ISheet sheet)> GetSheets(
            this IAccountStateDelta states,
            params Type[] sheetTypes)
        {
            Dictionary <Type, (Address address, ISheet sheet)> result = sheetTypes.ToDictionary(
                sheetType => sheetType,
                sheetType => (Addresses.GetSheetAddress(sheetType.Name), (ISheet)null));

#pragma warning disable LAA1002
            var addresses = result
                            .Select(tuple => tuple.Value.address)
                            .ToArray();
#pragma warning restore LAA1002
            var csvValues = states.GetStates(addresses);
            for (var i = 0; i < sheetTypes.Length; i++)
            {
                var sheetType = sheetTypes[i];
                var address   = addresses[i];
                var csvValue  = csvValues[i];
                if (csvValue is null)
                {
                    throw new FailedLoadStateException(address, sheetType);
                }

                var    csv = csvValue.ToDotnetString();
                byte[] hash;
                using (var sha256 = SHA256.Create())
                {
                    hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(csv));
                }

                var cacheKey = address.ToHex() + ByteUtil.Hex(hash);
                if (SheetsCache.TryGetValue(cacheKey, out var cached))
                {
                    result[sheetType] = (address, cached);
                    continue;
                }

                var sheetConstructorInfo = sheetType.GetConstructor(Type.EmptyTypes);
                if (!(sheetConstructorInfo?.Invoke(Array.Empty <object>()) is ISheet sheet))
                {
                    throw new FailedLoadSheetException(sheetType);
                }

                sheet.Set(csv);
                SheetsCache.AddOrUpdate(cacheKey, sheet);
                result[sheetType] = (address, sheet);
            }

            return(result);
        }
        public static Dictionary <Address, IValue> GetStatesAsDict(this IAccountStateDelta states, params Address[] addresses)
        {
            var result = new Dictionary <Address, IValue>();
            var values = states.GetStates(addresses);

            for (var i = 0; i < addresses.Length; i++)
            {
                var address = addresses[i];
                var value   = values[i];
                result[address] = value ?? Null.Value;
            }

            return(result);
        }
        public static AvatarState GetAvatarStateV2(this IAccountStateDelta states, Address address)
        {
            var addresses = new List <Address>
            {
                address,
            };

            string[] keys =
            {
                LegacyInventoryKey,
                LegacyWorldInformationKey,
                LegacyQuestListKey,
            };
            addresses.AddRange(keys.Select(key => address.Derive(key)));
            var serializedValues = states.GetStates(addresses);

            if (!(serializedValues[0] is Dictionary serializedAvatar))
            {
                Log.Warning("No avatar state ({AvatarAddress})", address.ToHex());
                return(null);
            }

            for (var i = 0; i < keys.Length; i++)
            {
                var key             = keys[i];
                var serializedValue = serializedValues[i + 1];
                if (serializedValue is null)
                {
                    throw new FailedLoadStateException($"failed to load {key}.");
                }

                serializedAvatar = serializedAvatar.SetItem(key, serializedValue);
            }

            try
            {
                return(new AvatarState(serializedAvatar));
            }
            catch (InvalidCastException e)
            {
                Log.Error(
                    e,
                    "Invalid avatar state ({AvatarAddress}): {SerializedAvatar}",
                    address.ToHex(),
                    serializedAvatar
                    );

                return(null);
            }
        }