Skip to content

Commit

Permalink
feat: ERC-6492 creation (#44)
Browse files Browse the repository at this point in the history
* feat: ERC-6492 signature verification

* chore: make separate client

* chore: remove unneded test code

* chore: fix docker image platform

* fix: verify full hashes

* chore: improve error handling

* chore: bindings

* feat: ERC-6492 creation

* chore: progress

* fix: get it working

* fix: refactor sign to not require passing private key

* chore: bump erc6492

* chore: test partially deployed

* chore: rearrange

* chore: remove unwraps

* chore: fmt
  • Loading branch information
chris13524 authored Nov 6, 2024
1 parent 147cc96 commit d27c3e5
Show file tree
Hide file tree
Showing 11 changed files with 951 additions and 85 deletions.
2 changes: 1 addition & 1 deletion crates/ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ swift-bridge = { git = "https://github.com/wooden-worm/swift-bridge.git", branch
"async",
] }
yttrium = { path = "../yttrium" }
erc6492 = { git = "https://github.com/reown-com/erc6492.git" }
erc6492 = { git = "https://github.com/reown-com/erc6492.git", branch = "chore/remove-foundry-install" }
alloy = { version = "0.3.6" }

# Errors
Expand Down
87 changes: 87 additions & 0 deletions crates/ffi/YttriumCore/Sources/YttriumCore/ffi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,43 @@ extension __swift_bridge__$Option$FFIAccountClientConfig {
}
}
}
public struct FFIPreparedSignature {
public var hash: RustString

public init(hash: RustString) {
self.hash = hash
}

@inline(__always)
func intoFfiRepr() -> __swift_bridge__$FFIPreparedSignature {
{ let val = self; return __swift_bridge__$FFIPreparedSignature(hash: { let rustString = val.hash.intoRustString(); rustString.isOwned = false; return rustString.ptr }()); }()
}
}
extension __swift_bridge__$FFIPreparedSignature {
@inline(__always)
func intoSwiftRepr() -> FFIPreparedSignature {
{ let val = self; return FFIPreparedSignature(hash: RustString(ptr: val.hash)); }()
}
}
extension __swift_bridge__$Option$FFIPreparedSignature {
@inline(__always)
func intoSwiftRepr() -> Optional<FFIPreparedSignature> {
if self.is_some {
return self.val.intoSwiftRepr()
} else {
return nil
}
}

@inline(__always)
static func fromSwiftRepr(_ val: Optional<FFIPreparedSignature>) -> __swift_bridge__$Option$FFIPreparedSignature {
if let v = val {
return __swift_bridge__$Option$FFIPreparedSignature(is_some: true, val: v.intoFfiRepr())
} else {
return __swift_bridge__$Option$FFIPreparedSignature(is_some: false, val: __swift_bridge__$FFIPreparedSignature())
}
}
}
public struct FFIPreparedSendTransaction {
public var hash: RustString
public var do_send_transaction_params: RustString
Expand Down Expand Up @@ -444,6 +481,56 @@ extension FFIAccountClientRef {
}
}

public func prepare_sign_message<GenericIntoRustString: IntoRustString>(_ _message_hash: GenericIntoRustString) async throws -> FFIPreparedSignature {
func onComplete(cbWrapperPtr: UnsafeMutableRawPointer?, rustFnRetVal: __swift_bridge__$ResultFFIPreparedSignatureAndFFIError) {
let wrapper = Unmanaged<CbWrapper$FFIAccountClient$prepare_sign_message>.fromOpaque(cbWrapperPtr!).takeRetainedValue()
switch rustFnRetVal.tag { case __swift_bridge__$ResultFFIPreparedSignatureAndFFIError$ResultOk: wrapper.cb(.success(rustFnRetVal.payload.ok.intoSwiftRepr())) case __swift_bridge__$ResultFFIPreparedSignatureAndFFIError$ResultErr: wrapper.cb(.failure(rustFnRetVal.payload.err.intoSwiftRepr())) default: fatalError() }
}

return try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation<FFIPreparedSignature, Error>) in
let callback = { rustFnRetVal in
continuation.resume(with: rustFnRetVal)
}

let wrapper = CbWrapper$FFIAccountClient$prepare_sign_message(cb: callback)
let wrapperPtr = Unmanaged.passRetained(wrapper).toOpaque()

__swift_bridge__$FFIAccountClient$prepare_sign_message(wrapperPtr, onComplete, ptr, { let rustString = _message_hash.intoRustString(); rustString.isOwned = false; return rustString.ptr }())
})
}
class CbWrapper$FFIAccountClient$prepare_sign_message {
var cb: (Result<FFIPreparedSignature, Error>) -> ()

public init(cb: @escaping (Result<FFIPreparedSignature, Error>) -> ()) {
self.cb = cb
}
}

public func do_sign_message<GenericIntoRustString: IntoRustString>(_ _signatures: RustVec<GenericIntoRustString>) async throws -> RustString {
func onComplete(cbWrapperPtr: UnsafeMutableRawPointer?, rustFnRetVal: __swift_bridge__$ResultStringAndFFIError) {
let wrapper = Unmanaged<CbWrapper$FFIAccountClient$do_sign_message>.fromOpaque(cbWrapperPtr!).takeRetainedValue()
switch rustFnRetVal.tag { case __swift_bridge__$ResultStringAndFFIError$ResultOk: wrapper.cb(.success(RustString(ptr: rustFnRetVal.payload.ok))) case __swift_bridge__$ResultStringAndFFIError$ResultErr: wrapper.cb(.failure(rustFnRetVal.payload.err.intoSwiftRepr())) default: fatalError() }
}

return try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation<RustString, Error>) in
let callback = { rustFnRetVal in
continuation.resume(with: rustFnRetVal)
}

let wrapper = CbWrapper$FFIAccountClient$do_sign_message(cb: callback)
let wrapperPtr = Unmanaged.passRetained(wrapper).toOpaque()

__swift_bridge__$FFIAccountClient$do_sign_message(wrapperPtr, onComplete, ptr, { let val = _signatures; val.isOwned = false; return val.ptr }())
})
}
class CbWrapper$FFIAccountClient$do_sign_message {
var cb: (Result<RustString, Error>) -> ()

public init(cb: @escaping (Result<RustString, Error>) -> ()) {
self.cb = cb
}
}

public func send_transactions<GenericIntoRustString: IntoRustString>(_ _transactions: RustVec<GenericIntoRustString>) async throws -> RustString {
func onComplete(cbWrapperPtr: UnsafeMutableRawPointer?, rustFnRetVal: __swift_bridge__$ResultStringAndFFIError) {
let wrapper = Unmanaged<CbWrapper$FFIAccountClient$send_transactions>.fromOpaque(cbWrapperPtr!).takeRetainedValue()
Expand Down
136 changes: 135 additions & 1 deletion crates/ffi/src/account_client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
use super::ffi;
use super::ffi::{FFIAccountClientConfig, FFIError};
use crate::ffi::{FFIOwnerSignature, FFIPreparedSendTransaction};
use crate::{
ffi::{
FFIOwnerSignature, FFIPreparedSendTransaction, FFIPreparedSignature,
},
FFIPreparedSign, FFIPreparedSignStep3,
};
use alloy::primitives::B256;
use alloy::sol_types::SolStruct;
use std::str::FromStr;
use yttrium::smart_accounts::safe::SignOutputEnum;
use yttrium::transaction::send::safe_test::{
Address, OwnerSignature, Signature,
};
Expand Down Expand Up @@ -106,6 +115,131 @@ impl FFIAccountClient {
.map_err(|e| FFIError::Unknown(e.to_string()))
}

pub async fn prepare_sign_message(
&self,
message_hash: String,
) -> Result<FFIPreparedSignature, FFIError> {
let message_hash = B256::from_str(&message_hash)
.map_err(|e| FFIError::Unknown(e.to_string()))?;

let prepared_signature =
self.account_client.prepare_sign_message(message_hash);

Ok(FFIPreparedSignature {
hash: prepared_signature
.safe_message
.eip712_signing_hash(&prepared_signature.domain)
.to_string(),
})
}

pub async fn do_sign_message(
&self,
signatures: Vec<String>,
) -> Result<String, FFIError> {
let signatures: Result<Vec<FFIOwnerSignature>, _> = signatures
.into_iter()
.map(|json| serde_json::from_str::<FFIOwnerSignature>(&json))
.collect();

let signatures = match signatures {
Ok(sigs) => sigs,
Err(e) => {
return Err(FFIError::Unknown(format!(
"Failed to deserialize signatures: {}",
e
)));
}
};

let mut signatures2 = Vec::with_capacity(signatures.len());
for signature in signatures {
signatures2.push(OwnerSignature {
owner: signature
.owner
.parse::<Address>()
.map_err(|e| FFIError::Unknown(e.to_string()))?,
signature: signature
.signature
.parse::<Signature>()
.map_err(|e| FFIError::Unknown(e.to_string()))?,
});
}

let result = self
.account_client
.do_sign_message(signatures2)
.await
.map_err(|e| FFIError::Unknown(e.to_string()))?;

let ffi_output = match result {
SignOutputEnum::Signature(signature) => FFIPreparedSign {
signature: Some(signature),
sign_step_3: None,
},
SignOutputEnum::SignOutput(so) => FFIPreparedSign {
signature: None,
sign_step_3: Some(FFIPreparedSignStep3 {
hash: so.to_sign.hash,
sign_step_3_params: serde_json::to_string(
&so.sign_step_3_params,
)
.map_err(|e| FFIError::Unknown(e.to_string()))?,
}),
},
};

let serialized = serde_json::to_string(&ffi_output)
.map_err(|e| FFIError::Unknown(e.to_string()))?;

Ok(serialized)
}

pub async fn finalize_sign_message(
&self,
signatures: Vec<String>,
sign_step_3_params: String,
) -> Result<String, FFIError> {
let signatures: Result<Vec<FFIOwnerSignature>, _> = signatures
.into_iter()
.map(|json| serde_json::from_str::<FFIOwnerSignature>(&json))
.collect();

let signatures = match signatures {
Ok(sigs) => sigs,
Err(e) => {
return Err(FFIError::Unknown(format!(
"Failed to deserialize signatures: {}",
e
)));
}
};

let mut signatures2 = Vec::with_capacity(signatures.len());
for signature in signatures {
signatures2.push(OwnerSignature {
owner: signature
.owner
.parse::<Address>()
.map_err(|e| FFIError::Unknown(e.to_string()))?,
signature: signature
.signature
.parse::<Signature>()
.map_err(|e| FFIError::Unknown(e.to_string()))?,
});
}

let sign_step_3_params = serde_json::from_str(&sign_step_3_params)
.map_err(|e| FFIError::Unknown(e.to_string()))?;

Ok(self
.account_client
.finalize_sign_message(signatures2, sign_step_3_params)
.await
.map_err(|e| FFIError::Unknown(e.to_string()))?
.to_string())
}

pub async fn send_transactions(
&self,
transactions: Vec<String>,
Expand Down
31 changes: 31 additions & 0 deletions crates/ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#![allow(dead_code, improper_ctypes, clippy::unnecessary_cast)]
use alloy::primitives::{Bytes, B256};
use serde::{Deserialize, Serialize};

use self::account_client::FFIAccountClient;
use self::account_client_eip7702::FFI7702AccountClient;
use self::erc6492_client::Erc6492Client;
Expand All @@ -10,6 +13,18 @@ pub mod erc6492_client;
pub mod error;
pub mod log;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FFIPreparedSign {
pub signature: Option<Bytes>,
pub sign_step_3: Option<FFIPreparedSignStep3>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FFIPreparedSignStep3 {
pub hash: B256,
pub sign_step_3_params: String,
}

#[allow(non_camel_case_types)]
#[swift_bridge::bridge]
mod ffi {
Expand Down Expand Up @@ -52,6 +67,12 @@ mod ffi {
pub safe: bool,
}

#[derive(Debug, Clone)]
#[swift_bridge(swift_repr = "struct")]
pub struct FFIPreparedSignature {
pub hash: String,
}

#[derive(Debug, Clone)]
#[swift_bridge(swift_repr = "struct")]
pub struct FFIPreparedSendTransaction {
Expand Down Expand Up @@ -86,6 +107,16 @@ mod ffi {

pub async fn get_address(&self) -> Result<String, FFIError>;

pub async fn prepare_sign_message(
&self,
_message_hash: String,
) -> Result<FFIPreparedSignature, FFIError>;

pub async fn do_sign_message(
&self,
_signatures: Vec<String>,
) -> Result<String, FFIError>;

pub async fn send_transactions(
&self,
_transactions: Vec<String>,
Expand Down
1 change: 1 addition & 0 deletions crates/yttrium/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ alloy = { version = "0.3.6", features = [
"eip712",
] }
alloy-provider = { version = "0.3.6", features = ["erc4337-api"] }
erc6492 = { git = "https://github.com/reown-com/erc6492.git", branch = "feat/create-6492" }
# foundry-block-explorers = "0.2.3"
getrandom = { version = "0.2", features = ["js"] }

Expand Down
Loading

0 comments on commit d27c3e5

Please sign in to comment.