From 875a7f78ccb0958bd16b2e8f3b3a7b3c89e94e6b Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Sun, 3 Nov 2024 14:51:04 +0800 Subject: [PATCH] pvm host-call updates (#215) * update * pass test vector first --- .../Blockchain/Config/ProtocolConfig.swift | 2 +- .../RuntimeProtocols/AccumulateFunction.swift | 85 +++--- .../RuntimeProtocols/Accumulation.swift | 29 +-- .../Sources/Blockchain/Types/State.swift | 16 +- .../VMInvocations/HostCall/HostCalls.swift | 243 +++++++++--------- .../AccumulateContext.swift | 40 +-- .../IsAuthorizedContext.swift | 2 +- .../OnTransferContext.swift | 11 +- .../Invocations/AccumulateInvocation.swift | 72 +++--- .../Invocations/IsAuthorizedInvocation.swift | 2 +- PolkaVM/Sources/PolkaVM/Registers.swift | 8 +- PolkaVM/Sources/PolkaVM/invokePVM.swift | 12 +- 12 files changed, 233 insertions(+), 289 deletions(-) diff --git a/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift b/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift index 14e8aee0..3d41b524 100644 --- a/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift +++ b/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift @@ -49,7 +49,7 @@ public struct ProtocolConfig: Sendable, Codable, Equatable { // L = 14, 400: The maximum age in timeslots of the lookup anchor. public var maxLookupAnchorAge: Int - // M = 128: The size of a transfer memo in octets. + // WT = 128: The size of a transfer memo in octets. public var transferMemoSize: Int // N = 2: The number of ticket entries per validator. diff --git a/Blockchain/Sources/Blockchain/RuntimeProtocols/AccumulateFunction.swift b/Blockchain/Sources/Blockchain/RuntimeProtocols/AccumulateFunction.swift index 7fcbe2d5..02ed3f67 100644 --- a/Blockchain/Sources/Blockchain/RuntimeProtocols/AccumulateFunction.swift +++ b/Blockchain/Sources/Blockchain/RuntimeProtocols/AccumulateFunction.swift @@ -36,10 +36,16 @@ public struct DeferredTransfers: Codable { } } -public struct AccumlateResultContext { - // s: updated current account - public var account: ServiceAccount? - // c +/// U: a characterization (i.e. values capable of representing) of state components +/// which are both needed and mutable by the accumulation process. +public struct AccumulateState { + /// d + public var serviceAccounts: [ServiceIndex: ServiceAccount] + /// i + public var validatorQueue: ConfigFixedSizeArray< + ValidatorKey, ProtocolConfig.TotalNumberOfValidators + > + /// q public var authorizationQueue: ConfigFixedSizeArray< ConfigFixedSizeArray< Data32, @@ -47,67 +53,36 @@ public struct AccumlateResultContext { >, ProtocolConfig.TotalNumberOfCores > - // v - public var validatorQueue: ConfigFixedSizeArray< - ValidatorKey, ProtocolConfig.TotalNumberOfValidators - > - // i - public var serviceIndex: ServiceIndex - // t - public var transfers: [DeferredTransfers] - // n - public var newAccounts: [ServiceIndex: ServiceAccount] - // p + /// x public var privilegedServices: PrivilegedServices +} - public init( - account: ServiceAccount?, - authorizationQueue: ConfigFixedSizeArray< - ConfigFixedSizeArray< - Data32, - ProtocolConfig.MaxAuthorizationsQueueItems - >, - ProtocolConfig.TotalNumberOfCores - >, - validatorQueue: ConfigFixedSizeArray< - ValidatorKey, ProtocolConfig.TotalNumberOfValidators - >, - serviceIndex: ServiceIndex, - transfers: [DeferredTransfers], - newAccounts: [ServiceIndex: ServiceAccount], - privilegedServices: PrivilegedServices - ) { - self.account = account - self.authorizationQueue = authorizationQueue - self.validatorQueue = validatorQueue - self.serviceIndex = serviceIndex - self.transfers = transfers - self.newAccounts = newAccounts - self.privilegedServices = privilegedServices - } +/// X +public struct AccumlateResultContext { + /// d + public var serviceAccounts: [ServiceIndex: ServiceAccount] + /// s: the accumulating service account index + public var serviceIndex: ServiceIndex + /// u + public var accumulateState: AccumulateState + /// i + public var nextAccountIndex: ServiceIndex + /// t: deferred transfers + public var transfers: [DeferredTransfers] } public protocol AccumulateFunction { func invoke( config: ProtocolConfigRef, + // u + state: AccumulateState, + // s serviceIndex: ServiceIndex, - code: Data, - serviceAccounts: [ServiceIndex: ServiceAccount], + // g gas: Gas, + // o arguments: [AccumulateArguments], - // other inputs needed (not directly in GP's Accumulation function signature) - validatorQueue: ConfigFixedSizeArray< - ValidatorKey, ProtocolConfig.TotalNumberOfValidators - >, - authorizationQueue: ConfigFixedSizeArray< - ConfigFixedSizeArray< - Data32, - ProtocolConfig.MaxAuthorizationsQueueItems - >, - ProtocolConfig.TotalNumberOfCores - >, - privilegedServices: PrivilegedServices, initialIndex: ServiceIndex, timeslot: TimeslotIndex - ) throws -> (ctx: AccumlateResultContext, result: Data32?) + ) throws -> (state: AccumulateState, transfers: [DeferredTransfers], result: Data32?, gas: Gas) } diff --git a/Blockchain/Sources/Blockchain/RuntimeProtocols/Accumulation.swift b/Blockchain/Sources/Blockchain/RuntimeProtocols/Accumulation.swift index 7148d09b..333a8844 100644 --- a/Blockchain/Sources/Blockchain/RuntimeProtocols/Accumulation.swift +++ b/Blockchain/Sources/Blockchain/RuntimeProtocols/Accumulation.swift @@ -107,20 +107,17 @@ extension Accumulation { assertionFailure("unreachable: service not found") throw AccumulationError.invalidServiceIndex } - let acc = try serviceAccounts[service].unwrap(orError: AccumulationError.invalidServiceIndex) - guard let code = acc.preimages[acc.codeHash] else { - continue - } - let (ctx, commitment) = try accumlateFunction.invoke( + let (newState, transfers, commitment, _) = try accumlateFunction.invoke( config: config, + state: AccumulateState( + serviceAccounts: serviceAccounts, + validatorQueue: validatorQueue, + authorizationQueue: authorizationQueue, + privilegedServices: privilegedServices + ), serviceIndex: service, - code: code, - serviceAccounts: serviceAccounts, gas: gas, arguments: arguments, - validatorQueue: validatorQueue, - authorizationQueue: authorizationQueue, - privilegedServices: privilegedServices, initialIndex: Blake2b256.hash(service.encode(), entropyPool.t0.data, block.header.timeslot.encode()) .data.decode(UInt32.self), timeslot: block.header.timeslot @@ -129,27 +126,25 @@ extension Accumulation { commitments.append((service, commitment)) } - for (service, account) in ctx.newAccounts { + for (service, account) in newState.serviceAccounts { guard newServiceAccounts[service] == nil else { throw AccumulationError.duplicatedServiceIndex } newServiceAccounts[service] = account } - newServiceAccounts[service] = ctx.account - switch service { case privilegedServices.empower: - newPrivilegedServices = ctx.privilegedServices + newPrivilegedServices = newState.privilegedServices case privilegedServices.assign: - newAuthorizationQueue = ctx.authorizationQueue + newAuthorizationQueue = newState.authorizationQueue case privilegedServices.designate: - newValidatorQueue = ctx.validatorQueue + newValidatorQueue = newState.validatorQueue default: break } - for transfer in ctx.transfers { + for transfer in transfers { transferReceivers[transfer.sender, default: []].append(transfer) } } diff --git a/Blockchain/Sources/Blockchain/Types/State.swift b/Blockchain/Sources/Blockchain/Types/State.swift index d2f0aa4e..232f1f24 100644 --- a/Blockchain/Sources/Blockchain/Types/State.swift +++ b/Blockchain/Sources/Blockchain/Types/State.swift @@ -215,25 +215,13 @@ extension State: Guaranteeing { struct DummyFunction: AccumulateFunction, OnTransferFunction { func invoke( config _: ProtocolConfigRef, + state _: AccumulateState, serviceIndex _: ServiceIndex, - code _: Data, - serviceAccounts _: [ServiceIndex: ServiceAccount], gas _: Gas, arguments _: [AccumulateArguments], - validatorQueue _: ConfigFixedSizeArray< - ValidatorKey, ProtocolConfig.TotalNumberOfValidators - >, - authorizationQueue _: ConfigFixedSizeArray< - ConfigFixedSizeArray< - Data32, - ProtocolConfig.MaxAuthorizationsQueueItems - >, - ProtocolConfig.TotalNumberOfCores - >, - privilegedServices _: PrivilegedServices, initialIndex _: ServiceIndex, timeslot _: TimeslotIndex - ) throws -> (ctx: AccumlateResultContext, result: Data32?) { + ) throws -> (state: AccumulateState, transfers: [DeferredTransfers], result: Data32?, gas: Gas) { fatalError("not implemented") } diff --git a/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift b/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift index 036e46f1..584a13a8 100644 --- a/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift +++ b/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift @@ -10,8 +10,8 @@ public class GasFn: HostCall { public static var identifier: UInt8 { 0 } public func _callImpl(config _: ProtocolConfigRef, state: VMState) throws { - state.writeRegister(Registers.Index(raw: 0), UInt32(bitPattern: Int32(state.getGas().value & 0xFFFF_FFFF))) - state.writeRegister(Registers.Index(raw: 1), UInt32(bitPattern: Int32(state.getGas().value >> 32))) + state.writeRegister(Registers.Index(raw: 7), UInt32(bitPattern: Int32(state.getGas().value & 0xFFFF_FFFF))) + state.writeRegister(Registers.Index(raw: 8), UInt32(bitPattern: Int32(state.getGas().value >> 32))) } } @@ -31,14 +31,14 @@ public class Lookup: HostCall { public func _callImpl(config _: ProtocolConfigRef, state: VMState) throws { var account: ServiceAccount? - let reg0 = state.readRegister(Registers.Index(raw: 0)) - if reg0 == serviceIndex || reg0 == Int32.max { + let reg = state.readRegister(Registers.Index(raw: 7)) + if reg == serviceIndex || reg == Int32.max { account = serviceAccount } else { - account = serviceAccounts[reg0] + account = serviceAccounts[reg] } - let regs = state.readRegisters(in: 1 ..< 4) + let regs = state.readRegisters(in: 8 ..< 11) let preimageHash = try? Blake2b256.hash(state.readMemory(address: regs[0], length: 32)) @@ -56,12 +56,12 @@ public class Lookup: HostCall { if preimageHash != nil, isWritable { if let value { - state.writeRegister(Registers.Index(raw: 0), UInt32(value.count)) + state.writeRegister(Registers.Index(raw: 7), UInt32(value.count)) } else { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.NONE.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.NONE.rawValue) } } else { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OOB.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OOB.rawValue) } } } @@ -82,14 +82,14 @@ public class Read: HostCall { public func _callImpl(config _: ProtocolConfigRef, state: VMState) throws { var account: ServiceAccount? - let reg0 = state.readRegister(Registers.Index(raw: 0)) - if reg0 == serviceIndex || reg0 == Int32.max { + let reg = state.readRegister(Registers.Index(raw: 7)) + if reg == serviceIndex || reg == Int32.max { account = serviceAccount } else { - account = serviceAccounts[reg0] + account = serviceAccounts[reg] } - let regs = state.readRegisters(in: 1 ..< 5) + let regs = state.readRegisters(in: 8 ..< 12) let key = try? Blake2b256.hash(serviceIndex.encode(), state.readMemory(address: regs[0], length: Int(regs[1]))) @@ -107,12 +107,12 @@ public class Read: HostCall { if key != nil, isWritable { if let value { - state.writeRegister(Registers.Index(raw: 0), UInt32(value.count)) + state.writeRegister(Registers.Index(raw: 7), UInt32(value.count)) } else { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.NONE.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.NONE.rawValue) } } else { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OOB.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OOB.rawValue) } } } @@ -130,7 +130,7 @@ public class Write: HostCall { } public func _callImpl(config: ProtocolConfigRef, state: VMState) throws { - let regs = state.readRegisters(in: 0 ..< 4) + let regs = state.readRegisters(in: 7 ..< 11) let key = try? Blake2b256.hash(serviceIndex.encode(), state.readMemory(address: regs[0], length: Int(regs[1]))) @@ -153,12 +153,12 @@ public class Write: HostCall { } if key != nil, let account, account.thresholdBalance(config: config) <= account.balance { - state.writeRegister(Registers.Index(raw: 0), l) + state.writeRegister(Registers.Index(raw: 7), l) serviceAccount = account } else if let account, account.thresholdBalance(config: config) > account.balance { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.FULL.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.FULL.rawValue) } else { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OOB.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OOB.rawValue) } } } @@ -167,35 +167,27 @@ public class Write: HostCall { public class Info: HostCall { public static var identifier: UInt8 { 4 } - public let serviceAccount: ServiceAccount public let serviceIndex: ServiceIndex public let serviceAccounts: [ServiceIndex: ServiceAccount] - // only used in accumulation x.n - public let newServiceAccounts: [ServiceIndex: ServiceAccount] public init( - account: ServiceAccount, serviceIndex: ServiceIndex, - accounts: [ServiceIndex: ServiceAccount], - newAccounts: [ServiceIndex: ServiceAccount] + accounts: [ServiceIndex: ServiceAccount] ) { - serviceAccount = account self.serviceIndex = serviceIndex serviceAccounts = accounts - newServiceAccounts = newAccounts } public func _callImpl(config: ProtocolConfigRef, state: VMState) throws { var account: ServiceAccount? - let reg0 = state.readRegister(Registers.Index(raw: 0)) - if reg0 == serviceIndex || reg0 == Int32.max { - account = serviceAccount + let reg = state.readRegister(Registers.Index(raw: 7)) + if reg == Int32.max { + account = serviceAccounts[serviceIndex] } else { - let accounts = serviceAccounts.merging(newServiceAccounts) { _, new in new } - account = accounts[reg0] + account = serviceAccounts[reg] } - let o = state.readRegister(Registers.Index(raw: 1)) + let o = state.readRegister(Registers.Index(raw: 8)) let m: Data? if let account { @@ -216,11 +208,11 @@ public class Info: HostCall { if let m, state.isMemoryWritable(address: o, length: Int(m.count)) { try state.writeMemory(address: o, values: m) - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OK.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OK.rawValue) } else if m == nil { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.NONE.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.NONE.rawValue) } else { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OOB.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OOB.rawValue) } } } @@ -238,7 +230,7 @@ public class Empower: HostCall { } public func _callImpl(config _: ProtocolConfigRef, state: VMState) throws { - let regs = state.readRegisters(in: 0 ..< 5) + let regs = state.readRegisters(in: 7 ..< 12) var basicGas: [ServiceIndex: Gas] = [:] let length = 12 * Int(regs[4]) @@ -252,18 +244,18 @@ public class Empower: HostCall { } if basicGas.count != 0 { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OK.rawValue) - x.privilegedServices.empower = regs[0] - x.privilegedServices.assign = regs[1] - x.privilegedServices.designate = regs[2] - x.privilegedServices.basicGas = basicGas + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OK.rawValue) + x.accumulateState.privilegedServices.empower = regs[0] + x.accumulateState.privilegedServices.assign = regs[1] + x.accumulateState.privilegedServices.designate = regs[2] + x.accumulateState.privilegedServices.basicGas = basicGas } else { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OOB.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OOB.rawValue) } } } -/// Set authorization queue for a service account +/// Assign the authorization queue for a core public class Assign: HostCall { public static var identifier: UInt8 { 6 } @@ -274,7 +266,7 @@ public class Assign: HostCall { } public func _callImpl(config: ProtocolConfigRef, state: VMState) throws { - let (targetCoreIndex, startAddr) = state.readRegister(Registers.Index(raw: 0), Registers.Index(raw: 1)) + let (targetCoreIndex, startAddr) = state.readRegister(Registers.Index(raw: 7), Registers.Index(raw: 8)) var authorizationQueue: [Data32] = [] let length = 32 * config.value.maxAuthorizationsQueueItems @@ -286,17 +278,17 @@ public class Assign: HostCall { } if targetCoreIndex < config.value.totalNumberOfCores, !authorizationQueue.isEmpty { - x.authorizationQueue[targetCoreIndex] = try ConfigFixedSizeArray(config: config, array: authorizationQueue) - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OK.rawValue) + x.accumulateState.authorizationQueue[targetCoreIndex] = try ConfigFixedSizeArray(config: config, array: authorizationQueue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OK.rawValue) } else if authorizationQueue.isEmpty { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OOB.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OOB.rawValue) } else { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.CORE.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.CORE.rawValue) } } } -/// Set validator queue for a service account +/// Designate the new validator queue public class Designate: HostCall { public static var identifier: UInt8 { 7 } @@ -307,7 +299,7 @@ public class Designate: HostCall { } public func _callImpl(config: ProtocolConfigRef, state: VMState) throws { - let startAddr = state.readRegister(Registers.Index(raw: 0)) + let startAddr = state.readRegister(Registers.Index(raw: 7)) var validatorQueue: [ValidatorKey] = [] let length = 336 * config.value.totalNumberOfValidators @@ -319,10 +311,10 @@ public class Designate: HostCall { } if !validatorQueue.isEmpty { - x.validatorQueue = try ConfigFixedSizeArray(config: config, array: validatorQueue) - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OK.rawValue) + x.accumulateState.validatorQueue = try ConfigFixedSizeArray(config: config, array: validatorQueue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OK.rawValue) } else { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OOB.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OOB.rawValue) } } } @@ -340,8 +332,8 @@ public class Checkpoint: HostCall { } public func _callImpl(config _: ProtocolConfigRef, state: VMState) throws { - state.writeRegister(Registers.Index(raw: 0), UInt32(bitPattern: Int32(state.getGas().value & 0xFFFF_FFFF))) - state.writeRegister(Registers.Index(raw: 1), UInt32(bitPattern: Int32(state.getGas().value >> 32))) + state.writeRegister(Registers.Index(raw: 7), UInt32(bitPattern: Int32(state.getGas().value & 0xFFFF_FFFF))) + state.writeRegister(Registers.Index(raw: 8), UInt32(bitPattern: Int32(state.getGas().value >> 32))) y = x } @@ -352,19 +344,21 @@ public class New: HostCall { public static var identifier: UInt8 { 9 } public var x: AccumlateResultContext + public var account: ServiceAccount public let accounts: [ServiceIndex: ServiceAccount] - public init(x: inout AccumlateResultContext, accounts: [ServiceIndex: ServiceAccount]) { + public init(x: inout AccumlateResultContext, account: ServiceAccount, accounts: [ServiceIndex: ServiceAccount]) { self.x = x + self.account = account self.accounts = accounts } - private static func bump(i: ServiceIndex) -> ServiceIndex { + private func bump(i: ServiceIndex) -> ServiceIndex { 256 + ((i - 256 + 42) & (serviceIndexModValue - 1)) } public func _callImpl(config: ProtocolConfigRef, state: VMState) throws { - let regs = state.readRegisters(in: 0 ..< 6) + let regs = state.readRegisters(in: 7 ..< 13) let codeHash: Data32? = try? Data32(state.readMemory(address: regs[0], length: 32)) let minAccumlateGas = Gas(0x1_0000_0000) * Gas(regs[3]) + Gas(regs[2]) @@ -384,17 +378,18 @@ public class New: HostCall { newAccount!.balance = newAccount!.thresholdBalance(config: config) } - let newBalance = (x.account?.balance ?? Balance(0)) - newAccount!.balance + if let newAccount { + account.balance -= newAccount.balance + } - if let newAccount, x.account != nil, newBalance >= x.account!.thresholdBalance(config: config) { - state.writeRegister(Registers.Index(raw: 0), x.serviceIndex) - x.newAccounts[x.serviceIndex] = newAccount - x.account!.balance = newBalance - x.serviceIndex = try AccumulateContext.check(i: New.bump(i: x.serviceIndex), serviceAccounts: accounts) + if let newAccount, account.balance >= account.thresholdBalance(config: config) { + state.writeRegister(Registers.Index(raw: 7), x.nextAccountIndex) + x.accumulateState.serviceAccounts.merge([x.nextAccountIndex: newAccount, x.serviceIndex: account]) { _, new in new } + x.nextAccountIndex = try AccumulateContext.check(i: bump(i: x.nextAccountIndex), serviceAccounts: accounts) } else if codeHash == nil { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OOB.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OOB.rawValue) } else { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.CASH.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.CASH.rawValue) } } } @@ -404,48 +399,46 @@ public class Upgrade: HostCall { public static var identifier: UInt8 { 10 } public var x: AccumlateResultContext - public let serviceIndex: ServiceIndex - public init(x: inout AccumlateResultContext, serviceIndex: ServiceIndex) { + public init(x: inout AccumlateResultContext) { self.x = x - self.serviceIndex = serviceIndex } public func _callImpl(config _: ProtocolConfigRef, state: VMState) throws { - let regs = state.readRegisters(in: 0 ..< 5) + let regs = state.readRegisters(in: 7 ..< 12) let codeHash: Data32? = try? Data32(state.readMemory(address: regs[0], length: 32)) let minAccumlateGas = Gas(0x1_0000_0000) * Gas(regs[1]) + Gas(regs[2]) let minOnTransferGas = Gas(0x1_0000_0000) * Gas(regs[3]) + Gas(regs[4]) if let codeHash { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OK.rawValue) - x.newAccounts[serviceIndex]?.codeHash = codeHash - x.newAccounts[serviceIndex]?.minAccumlateGas = minAccumlateGas - x.newAccounts[serviceIndex]?.minOnTransferGas = minOnTransferGas + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OK.rawValue) + x.accumulateState.serviceAccounts[x.serviceIndex]?.codeHash = codeHash + x.accumulateState.serviceAccounts[x.serviceIndex]?.minAccumlateGas = minAccumlateGas + x.accumulateState.serviceAccounts[x.serviceIndex]?.minOnTransferGas = minOnTransferGas } else { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OOB.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OOB.rawValue) } } } -/// Add a new transfer +/// Make a transfer public class Transfer: HostCall { public static var identifier: UInt8 { 11 } public var x: AccumlateResultContext - public let serviceIndex: ServiceIndex + public let account: ServiceAccount public let accounts: [ServiceIndex: ServiceAccount] - public init(x: inout AccumlateResultContext, serviceIndex: ServiceIndex, accounts: [ServiceIndex: ServiceAccount]) { + public init(x: inout AccumlateResultContext, account: ServiceAccount, accounts: [ServiceIndex: ServiceAccount]) { self.x = x - self.serviceIndex = serviceIndex + self.account = account self.accounts = accounts } public func gasCost(state: VMState) -> Gas { - let (reg1, reg2) = state.readRegister(Registers.Index(raw: 1), Registers.Index(raw: 2)) - return Gas(10) + Gas(reg1) + Gas(0x1_0000_0000) * Gas(reg2) + let (reg8, reg9) = state.readRegister(Registers.Index(raw: 8), Registers.Index(raw: 9)) + return Gas(10) + Gas(reg8) + Gas(0x1_0000_0000) * Gas(reg9) } public func _callImpl(config: ProtocolConfigRef, state: VMState) throws { @@ -454,30 +447,29 @@ public class Transfer: HostCall { let gasLimit = Gas(0x1_0000_0000) * Gas(regs[4]) + Gas(regs[3]) let memo = try? state.readMemory(address: regs[5], length: config.value.transferMemoSize) let dest = regs[0] - let allAccounts = accounts.merging(x.newAccounts) { _, new in new } - let newBalance = (x.account?.balance ?? Balance(0)) - amount + let newBalance = account.balance - amount if memo == nil { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OOB.rawValue) - } else if allAccounts[dest] == nil { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.WHO.rawValue) - } else if gasLimit < allAccounts[dest]!.minOnTransferGas { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.LOW.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OOB.rawValue) + } else if accounts[dest] == nil { + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.WHO.rawValue) + } else if gasLimit < accounts[dest]!.minOnTransferGas { + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.LOW.rawValue) } else if Gas(state.getGas()) < gasLimit { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.HIGH.rawValue) - } else if newBalance < x.account!.thresholdBalance(config: config) { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.CASH.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.HIGH.rawValue) + } else if newBalance < account.thresholdBalance(config: config) { + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.CASH.rawValue) } else { - x.account!.balance = newBalance + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OK.rawValue) x.transfers.append(DeferredTransfers( - sender: serviceIndex, + sender: x.serviceIndex, destination: dest, amount: amount, memo: Data128(memo!)!, gasLimit: gasLimit )) - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OK.rawValue) + x.accumulateState.serviceAccounts[x.serviceIndex]!.balance = newBalance } } } @@ -487,47 +479,44 @@ public class Quit: HostCall { public static var identifier: UInt8 { 12 } public var x: AccumlateResultContext - public let serviceIndex: ServiceIndex + public let account: ServiceAccount public let accounts: [ServiceIndex: ServiceAccount] - public init(x: inout AccumlateResultContext, serviceIndex: ServiceIndex, accounts: [ServiceIndex: ServiceAccount]) { + public init(x: inout AccumlateResultContext, account: ServiceAccount, accounts: [ServiceIndex: ServiceAccount]) { self.x = x - self.serviceIndex = serviceIndex + self.account = account self.accounts = accounts } public func gasCost(state: VMState) -> Gas { - let (reg1, reg2) = state.readRegister(Registers.Index(raw: 1), Registers.Index(raw: 2)) - return Gas(10) + Gas(reg1) + Gas(0x1_0000_0000) * Gas(reg2) + let (reg8, reg9) = state.readRegister(Registers.Index(raw: 8), Registers.Index(raw: 9)) + return Gas(10) + Gas(reg8) + Gas(0x1_0000_0000) * Gas(reg9) } public func _callImpl(config: ProtocolConfigRef, state: VMState) throws { - let (reg0, reg1) = state.readRegister(Registers.Index(raw: 0), Registers.Index(raw: 1)) - let allAccounts = accounts.merging(x.newAccounts) { _, new in new } - let amount = (x.account?.balance ?? Balance(0)) - x.account! - .thresholdBalance(config: config) + Balance(config.value.serviceMinBalance) + let (dest, startAddr) = state.readRegister(Registers.Index(raw: 7), Registers.Index(raw: 8)) + let amount = account.balance - account.thresholdBalance(config: config) + Balance(config.value.serviceMinBalance) let gasLimit = Gas(state.getGas()) - let dest = reg0 - let isValidDest = dest == serviceIndex || dest == Int32.max - let memoData = try? state.readMemory(address: reg1, length: config.value.transferMemoSize) + let isValidDest = dest == x.serviceIndex || dest == Int32.max + let memoData = try? state.readMemory(address: startAddr, length: config.value.transferMemoSize) let memo = memoData != nil ? try JamDecoder.decode(Data128.self, from: memoData!) : nil if isValidDest { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OK.rawValue) - x.account = nil + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OK.rawValue) + x.accumulateState.serviceAccounts.removeValue(forKey: x.serviceIndex) throw VMInvocationsError.forceHalt } else if memo == nil { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OOB.rawValue) - } else if allAccounts[dest] == nil { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.WHO.rawValue) - } else if gasLimit < allAccounts[dest]!.minOnTransferGas { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.LOW.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OOB.rawValue) + } else if accounts[dest] == nil { + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.WHO.rawValue) + } else if gasLimit < accounts[dest]!.minOnTransferGas { + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.LOW.rawValue) } else { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OK.rawValue) - x.account = nil + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OK.rawValue) + x.accumulateState.serviceAccounts.removeValue(forKey: x.serviceIndex) x.transfers.append(DeferredTransfers( - sender: serviceIndex, + sender: x.serviceIndex, destination: dest, amount: amount, memo: memo!, @@ -551,12 +540,12 @@ public class Solicit: HostCall { } public func _callImpl(config: ProtocolConfigRef, state: VMState) throws { - let (startAddr, length) = state.readRegister(Registers.Index(raw: 0), Registers.Index(raw: 1)) + let (startAddr, length) = state.readRegister(Registers.Index(raw: 7), Registers.Index(raw: 8)) let hash = try? state.readMemory(address: startAddr, length: 32) var account: ServiceAccount? if let hash { let hashAndLength = HashAndLength(hash: Data32(hash)!, length: length) - account = x.account + account = x.accumulateState.serviceAccounts[x.serviceIndex] if account?.preimageInfos[hashAndLength] == nil { account?.preimageInfos[hashAndLength] = [] } else if account?.preimageInfos[hashAndLength]!.count == 2 { @@ -565,13 +554,13 @@ public class Solicit: HostCall { } if hash == nil { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.OOB.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.OOB.rawValue) } else if account == nil { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.HUH.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.HUH.rawValue) } else if account!.balance < account!.thresholdBalance(config: config) { - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.FULL.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.FULL.rawValue) } else { - x.account = account + x.accumulateState.serviceAccounts[x.serviceIndex] = account } } } @@ -589,12 +578,12 @@ public class Forget: HostCall { } public func _callImpl(config: ProtocolConfigRef, state: VMState) throws { - let (startAddr, length) = state.readRegister(Registers.Index(raw: 0), Registers.Index(raw: 1)) + let (startAddr, length) = state.readRegister(Registers.Index(raw: 7), Registers.Index(raw: 8)) let hash = try? state.readMemory(address: startAddr, length: 32) var account: ServiceAccount? if let hash { let hashAndLength = HashAndLength(hash: Data32(hash)!, length: length) - account = x.account + account = x.accumulateState.serviceAccounts[x.serviceIndex] let value = account?.preimageInfos[hashAndLength] let minHoldPeriod = TimeslotIndex(config.value.preimagePurgePeriod) @@ -613,7 +602,7 @@ public class Forget: HostCall { } else if account == nil { state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.HUH.rawValue) } else { - x.account = account + x.accumulateState.serviceAccounts[x.serviceIndex] = account } } } diff --git a/Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/AccumulateContext.swift b/Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/AccumulateContext.swift index 2c3c37f3..bb6d2509 100644 --- a/Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/AccumulateContext.swift +++ b/Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/AccumulateContext.swift @@ -7,9 +7,7 @@ private let logger = Logger(label: "AccumulateContext") public class AccumulateContext: InvocationContext { public typealias ContextType = ( x: AccumlateResultContext, - y: AccumlateResultContext, // only set in checkpoint function - serviceIndex: ServiceIndex, - accounts: [ServiceIndex: ServiceAccount], + y: AccumlateResultContext, // only set in checkpoint host-call timeslot: TimeslotIndex ) @@ -23,29 +21,31 @@ public class AccumulateContext: InvocationContext { public func dispatch(index: UInt32, state: VMState) -> ExecOutcome { logger.debug("dispatching host-call: \(index)") - guard context.x.account != nil else { - fatalError("context x.account is nil") - } + + // the accumulating service account + var account = context.x.accumulateState.serviceAccounts[context.x.serviceIndex]! + + var allAccounts = context.x.serviceAccounts + allAccounts.merge(context.x.accumulateState.serviceAccounts) { _, new in new } + switch UInt8(index) { case Read.identifier: - return Read(account: context.x.account!, serviceIndex: context.serviceIndex, accounts: context.accounts) + return Read(account: account, serviceIndex: context.x.serviceIndex, accounts: allAccounts) .call(config: config, state: state) case Write.identifier: - return Write(account: &context.x.account!, serviceIndex: context.serviceIndex) + let execOutcome = Write(account: &account, serviceIndex: context.x.serviceIndex) .call(config: config, state: state) + // G function in Gray Paper + context.x.accumulateState.serviceAccounts[context.x.serviceIndex] = account + return execOutcome case Lookup.identifier: - return Lookup(account: context.x.account!, serviceIndex: context.serviceIndex, accounts: context.accounts) + return Lookup(account: account, serviceIndex: context.x.serviceIndex, accounts: allAccounts) .call(config: config, state: state) case GasFn.identifier: return GasFn().call(config: config, state: state) case Info.identifier: - return Info( - account: context.x.account!, - serviceIndex: context.serviceIndex, - accounts: context.accounts, - newAccounts: context.x.newAccounts - ) - .call(config: config, state: state) + return Info(serviceIndex: context.x.serviceIndex, accounts: allAccounts) + .call(config: config, state: state) case Empower.identifier: return Empower(x: &context.x).call(config: config, state: state) case Assign.identifier: @@ -55,15 +55,15 @@ public class AccumulateContext: InvocationContext { case Checkpoint.identifier: return Checkpoint(x: context.x, y: &context.y).call(config: config, state: state) case New.identifier: - return New(x: &context.x, accounts: context.accounts).call(config: config, state: state) + return New(x: &context.x, account: account, accounts: allAccounts).call(config: config, state: state) case Upgrade.identifier: - return Upgrade(x: &context.x, serviceIndex: context.serviceIndex) + return Upgrade(x: &context.x) .call(config: config, state: state) case Transfer.identifier: - return Transfer(x: &context.x, serviceIndex: context.serviceIndex, accounts: context.accounts) + return Transfer(x: &context.x, account: account, accounts: allAccounts) .call(config: config, state: state) case Quit.identifier: - return Quit(x: &context.x, serviceIndex: context.serviceIndex, accounts: context.accounts) + return Quit(x: &context.x, account: account, accounts: allAccounts) .call(config: config, state: state) case Solicit.identifier: return Solicit(x: &context.x, timeslot: context.timeslot).call(config: config, state: state) diff --git a/Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/IsAuthorizedContext.swift b/Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/IsAuthorizedContext.swift index 2152dbb3..66c74022 100644 --- a/Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/IsAuthorizedContext.swift +++ b/Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/IsAuthorizedContext.swift @@ -19,7 +19,7 @@ public class IsAuthorizedContext: InvocationContext { return GasFn().call(config: config, state: state) } else { state.consumeGas(Gas(10)) - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.WHAT.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.WHAT.rawValue) return .continued } } diff --git a/Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/OnTransferContext.swift b/Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/OnTransferContext.swift index d8323753..3317376c 100644 --- a/Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/OnTransferContext.swift +++ b/Blockchain/Sources/Blockchain/VMInvocations/InvocationContexts/OnTransferContext.swift @@ -34,16 +34,11 @@ public class OnTransferContext: InvocationContext { case GasFn.identifier: return GasFn().call(config: config, state: state) case Info.identifier: - return Info( - account: context.account, - serviceIndex: context.index, - accounts: context.accounts, - newAccounts: [:] - ) - .call(config: config, state: state) + return Info(serviceIndex: context.index, accounts: context.accounts) + .call(config: config, state: state) default: state.consumeGas(Gas(10)) - state.writeRegister(Registers.Index(raw: 0), HostCallResultCode.WHAT.rawValue) + state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.WHAT.rawValue) return .continued } } diff --git a/Blockchain/Sources/Blockchain/VMInvocations/Invocations/AccumulateInvocation.swift b/Blockchain/Sources/Blockchain/VMInvocations/Invocations/AccumulateInvocation.swift index 62ed3a9d..23b3ea4e 100644 --- a/Blockchain/Sources/Blockchain/VMInvocations/Invocations/AccumulateInvocation.swift +++ b/Blockchain/Sources/Blockchain/VMInvocations/Invocations/AccumulateInvocation.swift @@ -6,57 +6,59 @@ import Utils extension AccumulateFunction { public func invoke( config: ProtocolConfigRef, + state: AccumulateState, serviceIndex: ServiceIndex, - code _: Data, - serviceAccounts: [ServiceIndex: ServiceAccount], gas: Gas, arguments: [AccumulateArguments], - validatorQueue: ConfigFixedSizeArray< - ValidatorKey, ProtocolConfig.TotalNumberOfValidators - >, - authorizationQueue: ConfigFixedSizeArray< - ConfigFixedSizeArray< - Data32, - ProtocolConfig.MaxAuthorizationsQueueItems - >, - ProtocolConfig.TotalNumberOfCores - >, - privilegedServices: PrivilegedServices, initialIndex: ServiceIndex, timeslot: TimeslotIndex - ) throws -> (ctx: AccumlateResultContext, result: Data32?) { - var defaultCtx = AccumlateResultContext( - account: serviceAccounts[serviceIndex], - authorizationQueue: authorizationQueue, - validatorQueue: validatorQueue, - serviceIndex: serviceIndex, - transfers: [], - newAccounts: [:], - privilegedServices: privilegedServices + ) throws -> (state: AccumulateState, transfers: [DeferredTransfers], result: Data32?, gas: Gas) { + var serviceAccounts = state.serviceAccounts + + let defaultState = AccumulateState( + serviceAccounts: [:], + validatorQueue: state.validatorQueue, + authorizationQueue: state.authorizationQueue, + privilegedServices: state.privilegedServices ) if serviceAccounts[serviceIndex]?.codeHash.data == nil { - return (ctx: defaultCtx, result: nil) + return (defaultState, [], nil, Gas(0)) + } + + guard let accumulatingAccount = serviceAccounts[serviceIndex] else { + throw AccumulationError.invalidServiceIndex } - defaultCtx.serviceIndex = try AccumulateContext.check( - i: initialIndex & (serviceIndexModValue - 1) + 256, - serviceAccounts: serviceAccounts + serviceAccounts.removeValue(forKey: serviceIndex) + + let defaultCtx = try AccumlateResultContext( + serviceAccounts: serviceAccounts, + serviceIndex: serviceIndex, + accumulateState: AccumulateState( + serviceAccounts: [serviceIndex: accumulatingAccount], + validatorQueue: state.validatorQueue, + authorizationQueue: state.authorizationQueue, + privilegedServices: state.privilegedServices + ), + nextAccountIndex: AccumulateContext.check( + i: initialIndex & (serviceIndexModValue - 1) + 256, + serviceAccounts: [serviceIndex: accumulatingAccount] + ), + transfers: [] ) let ctx = AccumulateContext( context: ( x: defaultCtx, y: defaultCtx, - serviceIndex: serviceIndex, - accounts: serviceAccounts, timeslot: timeslot ), config: config ) let argument = try JamEncoder.encode(arguments) - let (exitReason, _, _, output) = invokePVM( + let (exitReason, gas, output) = invokePVM( config: config, blob: serviceAccounts[serviceIndex]!.codeHash.data, pc: 10, @@ -65,23 +67,23 @@ extension AccumulateFunction { ctx: ctx ) - return try collapse(exitReason: exitReason, output: output, context: ctx.context) + return try collapse(exitReason: exitReason, output: output, context: ctx.context, gas: gas) } // collapse function C selects one of the two dimensions of context depending on whether the virtual // machine’s halt was regular or exceptional private func collapse( - exitReason: ExitReason, output: Data?, context: AccumulateContext.ContextType - ) throws -> (ctx: AccumlateResultContext, result: Data32?) { + exitReason: ExitReason, output: Data?, context: AccumulateContext.ContextType, gas: Gas + ) throws -> (state: AccumulateState, transfers: [DeferredTransfers], result: Data32?, gas: Gas) { switch exitReason { case .halt: if let output, let o = Data32(output) { - (ctx: context.x, result: o) + (context.x.accumulateState, context.x.transfers, o, gas) } else { - (ctx: context.x, result: nil) + (context.x.accumulateState, context.x.transfers, nil, gas) } default: - (ctx: context.y, result: nil) + (context.y.accumulateState, context.y.transfers, nil, gas) } } } diff --git a/Blockchain/Sources/Blockchain/VMInvocations/Invocations/IsAuthorizedInvocation.swift b/Blockchain/Sources/Blockchain/VMInvocations/Invocations/IsAuthorizedInvocation.swift index 457425d2..1446c595 100644 --- a/Blockchain/Sources/Blockchain/VMInvocations/Invocations/IsAuthorizedInvocation.swift +++ b/Blockchain/Sources/Blockchain/VMInvocations/Invocations/IsAuthorizedInvocation.swift @@ -15,7 +15,7 @@ extension IsAuthorizedFunction { let args = try JamEncoder.encode(package) + JamEncoder.encode(coreIndex) let ctx = IsAuthorizedContext(config: config) - let (exitReason, _, _, output) = invokePVM( + let (exitReason, _, output) = invokePVM( config: config, blob: package.authorizationCodeHash.data, pc: 0, diff --git a/PolkaVM/Sources/PolkaVM/Registers.swift b/PolkaVM/Sources/PolkaVM/Registers.swift index 0fdccd85..6bf7a690 100644 --- a/PolkaVM/Sources/PolkaVM/Registers.swift +++ b/PolkaVM/Sources/PolkaVM/Registers.swift @@ -55,10 +55,10 @@ public struct Registers: Equatable { /// standard program init registers public init(config: DefaultPvmConfig, argumentData: Data?) { - reg1 = UInt32(config.pvmProgramInitRegister1Value) - reg2 = UInt32(config.pvmProgramInitStackBaseAddress) - reg10 = UInt32(config.pvmProgramInitInputStartAddress) - reg11 = UInt32(argumentData?.count ?? 0) + self[Index(raw: 0)] = UInt32(config.pvmProgramInitRegister1Value) + self[Index(raw: 1)] = UInt32(config.pvmProgramInitStackBaseAddress) + self[Index(raw: 7)] = UInt32(config.pvmProgramInitInputStartAddress) + self[Index(raw: 8)] = UInt32(argumentData?.count ?? 0) } public subscript(index: Index) -> UInt32 { diff --git a/PolkaVM/Sources/PolkaVM/invokePVM.swift b/PolkaVM/Sources/PolkaVM/invokePVM.swift index 7b56ff86..9f2e5292 100644 --- a/PolkaVM/Sources/PolkaVM/invokePVM.swift +++ b/PolkaVM/Sources/PolkaVM/invokePVM.swift @@ -12,7 +12,7 @@ public func invokePVM( gas: Gas, argumentData: Data?, ctx: any InvocationContext -) -> (ExitReason, VMState?, Gas?, Data?) { +) -> (ExitReason, Gas, Data?) { do { let state = try VMState(standardProgramBlob: blob, pc: pc, gas: gas, argumentData: argumentData) let engine = Engine(config: config, invocationContext: ctx) @@ -20,19 +20,19 @@ public func invokePVM( switch exitReason { case .outOfGas: - return (.outOfGas, state, nil, nil) + return (.outOfGas, Gas(0), nil) case .halt: let (addr, len) = state.readRegister(Registers.Index(raw: 10), Registers.Index(raw: 11)) let output = try? state.readMemory(address: addr, length: Int(len)) - return (.halt, state, Gas(state.getGas()), output ?? Data()) + return (.halt, Gas(state.getGas()), output ?? Data()) default: - return (.panic(.trap), state, nil, nil) + return (.panic(.trap), Gas(0), nil) } } catch let e as StandardProgram.Error { logger.error("standard program initialization failed: \(e)") - return (.panic(.trap), nil, nil, nil) + return (.panic(.trap), Gas(0), nil) } catch let e { logger.error("unknown error: \(e)") - return (.panic(.trap), nil, nil, nil) + return (.panic(.trap), Gas(0), nil) } }