feat(server): ProposalKind::ApproveOneOffTransaction

This commit is contained in:
CleverWild
2026-06-13 21:32:23 +02:00
parent b2b159b16f
commit 99e2b841e9
7 changed files with 234 additions and 1 deletions

View File

@@ -8,7 +8,7 @@ use arbiter_server::{
db,
};
use arbiter_server::actors::vault::Bootstrap;
use arbiter_server::db::schema::{aead_encrypted, evm_basic_grant, evm_wallet, evm_wallet_access, operator_identity};
use arbiter_server::db::schema::{aead_encrypted, evm_basic_grant, evm_wallet, evm_wallet_access, operator_identity, proposal_result};
use diesel::{ExpressionMethods, QueryDsl, insert_into};
use diesel_async::RunQueryDsl;
@@ -588,6 +588,121 @@ async fn approve_persistent_grant_creates_basic_grant_row() {
assert_eq!(count, 1);
}
#[tokio::test]
async fn approve_one_off_transaction_stores_result() {
use arbiter_proto::proto::operator::governance::ApproveOneOffTransactionPayload;
use arbiter_server::actors::evm::{Generate, OperatorCreateGrant};
use arbiter_server::evm::policies::{
SharedGrantSettings, SpecificGrant, VolumeRateLimit, ether_transfer,
};
use alloy::primitives::{Address, U256};
use chrono::Duration;
use prost::Message as _;
let db = db::create_test_pool().await;
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
actors
.vault
.ask(Bootstrap { seal_key: KeyCell::from([0u8; 32]) })
.await
.unwrap();
let signing_key = authn::SigningKey::generate();
let op_id = register_operator(&db, &signing_key.public_key()).await;
// Create a real encrypted wallet
let (wallet_id, wallet_address) = actors.evm.ask(Generate {}).await.unwrap();
// Create a client and wallet_access
let client_key = authn::SigningKey::generate();
let client_id = insert_unapproved_client(&db, &client_key.public_key()).await;
let mut conn = db.get().await.unwrap();
let wallet_access_id: i32 = insert_into(evm_wallet_access::table)
.values((
evm_wallet_access::wallet_id.eq(wallet_id),
evm_wallet_access::client_id.eq(client_id),
))
.returning(evm_wallet_access::id)
.get_result(&mut conn)
.await
.unwrap();
drop(conn);
// Create a grant that permits ether transfer to address zero
let to_address = Address::ZERO;
actors
.evm
.ask(OperatorCreateGrant {
basic: SharedGrantSettings {
wallet_access_id,
chain: 1,
valid_from: None,
valid_until: None,
max_gas_fee_per_gas: None,
max_priority_fee_per_gas: None,
rate_limit: None,
},
grant: SpecificGrant::EtherTransfer(ether_transfer::Settings {
target: vec![to_address],
limit: VolumeRateLimit {
max_volume: U256::from(1_000_000_000_000_000_000u128),
window: Duration::hours(24),
},
}),
})
.await
.unwrap();
// Encode the one-off transaction payload
let payload = ApproveOneOffTransactionPayload {
client_id,
wallet_address: wallet_address.as_slice().to_vec(),
chain_id: 1,
nonce: 0,
gas_limit: 21000,
max_fee_per_gas: 1u128.to_be_bytes().to_vec(),
max_priority_fee_per_gas: 1u128.to_be_bytes().to_vec(),
to: to_address.as_slice().to_vec(),
value: U256::from(1u64).to_be_bytes_vec(),
input: vec![],
};
let proposal_id = actors
.proposal_manager
.ask(CreateProposal {
kind: ProposalKind::ApproveOneOffTransaction { payload_bytes: payload.encode_to_vec() },
initiator_id: op_id,
ttl_secs: None,
})
.await
.unwrap();
let msg = make_vote_message(proposal_id, true);
let sig = signing_key.sign_message(&msg, GOVERNANCE_CONTEXT).unwrap();
let outcome = actors
.proposal_manager
.ask(CastVote {
proposal_id,
operator_id: op_id,
approve: true,
signature: sig.to_bytes(),
})
.await
.unwrap();
assert_eq!(outcome, VoteOutcome::QuorumApproved);
let mut conn = db.get().await.unwrap();
let count: i64 = proposal_result::table
.filter(proposal_result::proposal_id.eq(proposal_id))
.count()
.get_result(&mut conn)
.await
.unwrap();
assert_eq!(count, 1);
}
#[tokio::test]
async fn replace_operator_inserts_identity_row() {
let db = db::create_test_pool().await;