diff --git a/packages/native-utils/common/src/channel.rs b/packages/native-utils/common/src/channel.rs index ac703ab..aeb3c13 100644 --- a/packages/native-utils/common/src/channel.rs +++ b/packages/native-utils/common/src/channel.rs @@ -18,7 +18,7 @@ pub struct SignedStateVarsWithHash { pub signature: RecoverableSignature } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct Channel { pub chain_id: Uint256, diff --git a/packages/native-utils/common/src/state.rs b/packages/native-utils/common/src/state.rs index 3d4f324..fbd2ce7 100644 --- a/packages/native-utils/common/src/state.rs +++ b/packages/native-utils/common/src/state.rs @@ -11,7 +11,7 @@ use super::types::*; use super::utils::*; use super::channel::*; -#[derive(Deserialize,PartialEq)] +#[derive(Deserialize,Serialize,PartialEq,Clone)] #[serde(rename_all = "camelCase")] pub struct AllocationItem { pub destination: Bytes32, @@ -39,7 +39,7 @@ impl Tokenize for AssetOutcomeType { } } -#[derive(Deserialize,PartialEq)] +#[derive(Deserialize, Serialize, PartialEq, Clone)] #[serde(rename_all = "camelCase")] pub struct AllocationAssetOutcome { pub asset_holder_address: Address, @@ -55,7 +55,7 @@ impl Tokenize for AllocationAssetOutcome { } } -#[derive(Deserialize, Serialize, PartialEq)] +#[derive(Deserialize, Serialize, PartialEq,Clone)] #[serde(rename_all = "camelCase")] pub struct Guarantee { pub target_channel_id: Bytes32, @@ -71,7 +71,7 @@ impl Tokenize for Guarantee { } } -#[derive(Deserialize,PartialEq)] +#[derive(Deserialize,Serialize,PartialEq,Clone)] #[serde(rename_all = "camelCase")] pub struct GuaranteeAssetOutcome { pub asset_holder_address: Address, @@ -87,7 +87,7 @@ impl Tokenize for GuaranteeAssetOutcome { } } -#[derive(Deserialize, PartialEq)] +#[derive(Deserialize, Serialize, PartialEq, Clone)] #[serde(untagged)] pub enum AssetOutcome { AllocationAssetOutcome(AllocationAssetOutcome), @@ -107,7 +107,7 @@ impl Tokenize for AssetOutcome { } } -#[derive(Deserialize, PartialEq)] +#[derive(Deserialize, Serialize, PartialEq, Clone)] #[serde(transparent)] pub struct Outcome(Vec); @@ -123,7 +123,7 @@ impl Tokenize for Outcome { } } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct State { pub turn_num: Uint48, @@ -215,6 +215,20 @@ impl State { } } + pub fn compute_next_state(&self, app_data: Bytes, outcome: Outcome) -> Result { + if self.turn_num.0 < 3 { + Err("State not in running stage") + } else { + let mut next_state = self.clone(); + next_state.turn_num.0 = self.turn_num.0 + 1; + next_state.app_data = app_data.clone(); + if !self.is_final { + next_state.outcome = outcome.clone(); + } + Ok(next_state) + } + } + fn _require_extra_implicit_checks(&self, to_state: &State) -> Result<(), &'static str> { if &self.turn_num.0 + 1 != to_state.turn_num.0 { Err("turnNum must increment by one") diff --git a/packages/native-utils/common/src/types.rs b/packages/native-utils/common/src/types.rs index 40efbdc..5448d07 100644 --- a/packages/native-utils/common/src/types.rs +++ b/packages/native-utils/common/src/types.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use ethereum_types::U256; use serde::de::{Error as SerdeError, *}; use serde::ser::*; +use serde_derive::*; use super::tokenize::*; @@ -19,7 +20,7 @@ impl ToHexString for Vec { } } -#[derive(PartialEq)] +#[derive(PartialEq, Clone)] pub struct Bytes(pub Vec); impl Deref for Bytes { @@ -56,7 +57,7 @@ impl Tokenize for Bytes { } } -#[derive(PartialEq)] +#[derive(PartialEq,Clone)] pub struct Bytes32(Vec); impl From<[u8; 32]> for Bytes32 { @@ -109,7 +110,7 @@ impl Tokenize for Bytes32 { } } -#[derive(PartialEq, PartialOrd)] +#[derive(PartialEq, PartialOrd, Clone)] pub struct Uint48(pub u64); impl<'de> Deserialize<'de> for Uint48 { @@ -121,13 +122,25 @@ impl<'de> Deserialize<'de> for Uint48 { } } +impl Serialize for Uint48 { + fn serialize( + &self, + serializer: S, + ) -> Result + where + S: Serializer, + { + serializer.serialize_u64(self.0) + } +} + impl Tokenize for Uint48 { fn tokenize(&self) -> Token { Token::Uint(self.0.into()) } } -#[derive(PartialEq)] +#[derive(PartialEq, Serialize, Clone)] pub struct Uint256(pub U256); impl From for Uint256 { diff --git a/packages/native-utils/lib/index.d.ts b/packages/native-utils/lib/index.d.ts index 9b479cd..176be3a 100644 --- a/packages/native-utils/lib/index.d.ts +++ b/packages/native-utils/lib/index.d.ts @@ -1,4 +1,4 @@ -import { Bytes32, Channel, State } from '@statechannels/nitro-protocol' +import { Bytes32, Channel, Outcome, State } from '@statechannels/nitro-protocol' /** * A Nitro state with its state hash and signature from signing the state. @@ -95,4 +95,13 @@ export function recoverAddress(state: State, signature: string): string * @param peer_update Next state suggested by peer * @param signature Peer's signature for next state. */ - export function validatePeerUpdate(state, peer_update, signature): string \ No newline at end of file + export function validatePeerUpdate(state, peer_update, signature): string + + /** + * Get My state from last state. + * + * @param last_state A Nitro state. + * @param app_data App data + * @param outcome Outcome + */ + export function computeNextState(last_state: State, app_data: String, outcome: Outcome): State \ No newline at end of file diff --git a/packages/native-utils/lib/index.native.js b/packages/native-utils/lib/index.native.js index c8ccf9c..d5aafff 100644 --- a/packages/native-utils/lib/index.native.js +++ b/packages/native-utils/lib/index.native.js @@ -12,6 +12,7 @@ const { recoverAddress, verifySignature, validatePeerUpdate, + computeNextState, } = require('../native/index.node') function unwrapResult({ Ok, Err }) { @@ -46,4 +47,6 @@ module.exports = { verifySignature: (hash, address, signature) => unwrapResult(verifySignature(hash, address, signature)), validatePeerUpdate: (state, peer_update, signature) => unwrapResult(validatePeerUpdate(state, peer_update, signature)), + + computeNextState: (last_state, app_data, outcome) => unwrapResult(computeNextState(last_state, app_data, outcome)), } diff --git a/packages/native-utils/lib/index.wasm.js b/packages/native-utils/lib/index.wasm.js index e2e9874..cb28938 100644 --- a/packages/native-utils/lib/index.wasm.js +++ b/packages/native-utils/lib/index.wasm.js @@ -11,7 +11,8 @@ const { signState, recoverAddress, verifySignature, - validatePeerUpdate, + validatePeerUpdate, + computeNextState, } = require('@statechannels/wasm-utils') module.exports = { @@ -36,4 +37,5 @@ module.exports = { recoverAddress, verifySignature, validatePeerUpdate, + computeNextState, } diff --git a/packages/native-utils/native/src/lib.rs b/packages/native-utils/native/src/lib.rs index e467962..e542562 100644 --- a/packages/native-utils/native/src/lib.rs +++ b/packages/native-utils/native/src/lib.rs @@ -41,4 +41,8 @@ export! { fn validatePeerUpdate(state: State, peer_update: State, peer_signature: Bytes) -> Result { state.validate_peer_update(peer_update, peer_signature) } + + fn computeNextState(last_state: State, app_data: Bytes, outcome: Outcome) -> Result { + last_state.compute_next_state(app_data, outcome) + } } diff --git a/packages/native-utils/tests/transition.test.ts b/packages/native-utils/tests/transition.test.ts index c42731d..39f864d 100644 --- a/packages/native-utils/tests/transition.test.ts +++ b/packages/native-utils/tests/transition.test.ts @@ -169,4 +169,37 @@ describe('Validate state transitions', () => { expect(() => native.validatePeerUpdate(currentState, peerState, nativeSigned.signature)).toThrow('Outcome change forbidden'); expect(() => wasm.validatePeerUpdate(currentState, peerState, nativeSigned.signature)).toThrow('Outcome change forbidden'); }) + + test('Compute next state', async () => { + const otheroutcome = [ + { + assetHolderAddress: '0x0000000000000000000000000000000000000000', + guarantee: { + targetChannelId: + '0x0000000000000000000000000000000000000000000000000000000000000000', + destinations: [ + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x2222222222222222222222222222222222222222222222220222222222222222', + ], + }, + }, + ] + + let currentState = { + ...CURRENT_STATE, + } + + const APPDATA = '0x0000000000000000000000000000000000000000000000000000000000000001' + + currentState.isFinal = false + currentState.turnNum = 5; + + let nextState = native.computeNextState(currentState,APPDATA, OUTCOME) + let wasmNextState = wasm.computeNextState(currentState,APPDATA, OUTCOME) + + expect(Number(nextState.channel.chainId)).toEqual(Number(currentState.channel.chainId)); + expect(nextState.appData).toEqual(APPDATA); + expect(Number(wasmNextState.channel.chainId)).toEqual(Number(currentState.channel.chainId)); + expect(wasmNextState.appData).toEqual(APPDATA); + }) }) diff --git a/wasm-utils/src/lib.rs b/wasm-utils/src/lib.rs index 82777c8..0041c56 100644 --- a/wasm-utils/src/lib.rs +++ b/wasm-utils/src/lib.rs @@ -25,6 +25,9 @@ extern "C" { #[wasm_bindgen(typescript_type = "StateSignature")] pub type JsStateSignature; + + #[wasm_bindgen(typescript_type = "Outcome")] + pub type JsOutcome; } #[wasm_bindgen(js_name = "getChannelId")] @@ -88,4 +91,13 @@ pub fn validate_peer_update(state: &JsState, peer_update: &JsState, signature: & let signature: Bytes = signature.into_serde().unwrap(); let result = state.validate_peer_update(peer_update, signature).map_err(JsValue::from)?; Ok(JsValue::from_serde(&result).unwrap().into()) +} + +#[wasm_bindgen(js_name = "computeNextState")] +pub fn compute_next_state(state: &JsState, app_data: &JsString, outcome: &JsOutcome) -> Result { + let state: State = state.into_serde().unwrap(); + let app_data: Bytes = app_data.into_serde().unwrap(); + let outcome: Outcome = outcome.into_serde().unwrap(); + let result = state.compute_next_state(app_data, outcome).map_err(JsValue::from)?; + Ok(JsValue::from_serde(&result).unwrap().into()) } \ No newline at end of file