Skip to content

Commit

Permalink
feat(axelar-gateway): add more queries (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
milapsheth authored Jan 24, 2025
1 parent b583c7f commit ca3b486
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 41 deletions.
57 changes: 28 additions & 29 deletions contracts/stellar-axelar-gateway/src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use soroban_sdk::crypto::Hash;
use soroban_sdk::{Bytes, BytesN, Env, Vec};
use stellar_axelar_std::ensure;
use stellar_axelar_std::events::Event;
Expand All @@ -17,7 +16,6 @@ pub fn initialize_auth(
) -> Result<(), ContractError> {
env.storage().instance().set(&DataKey::Epoch, &0_u64);

// TODO: Do we need to manually expose these in a query, or can it be read directly off of storage in Stellar?
env.storage().instance().set(
&DataKey::PreviousSignerRetention,
&previous_signer_retention,
Expand All @@ -40,6 +38,27 @@ pub fn initialize_auth(
Ok(())
}

pub fn domain_separator(env: &Env) -> BytesN<32> {
env.storage()
.instance()
.get(&DataKey::DomainSeparator)
.expect("domain_separator not found")
}

pub fn minimum_rotation_delay(env: &Env) -> u64 {
env.storage()
.instance()
.get(&DataKey::MinimumRotationDelay)
.expect("minimum_rotation_delay not found")
}

pub fn previous_signers_retention(env: &Env) -> u64 {
env.storage()
.instance()
.get(&DataKey::PreviousSignerRetention)
.expect("previous_signers_retention not found")
}

pub fn validate_proof(
env: &Env,
data_hash: &BytesN<32>,
Expand All @@ -55,14 +74,8 @@ pub fn validate_proof(

let is_latest_signers: bool = signers_epoch == current_epoch;

let previous_signers_retention: u64 = env
.storage()
.instance()
.get(&DataKey::PreviousSignerRetention)
.expect("previous_signers_retention not found");

ensure!(
current_epoch - signers_epoch <= previous_signers_retention,
current_epoch - signers_epoch <= previous_signers_retention(env),
ContractError::OutdatedSigners
);

Expand Down Expand Up @@ -136,28 +149,15 @@ pub fn signers_hash_by_epoch(env: &Env, epoch: u64) -> Result<BytesN<32>, Contra
.ok_or(ContractError::InvalidEpoch)
}

fn message_hash_to_sign(env: &Env, signers_hash: BytesN<32>, data_hash: &BytesN<32>) -> Hash<32> {
let domain_separator: BytesN<32> = env
.storage()
.instance()
.get(&DataKey::DomainSeparator)
.unwrap();

let mut msg: Bytes = domain_separator.into();
fn message_hash_to_sign(env: &Env, signers_hash: BytesN<32>, data_hash: &BytesN<32>) -> BytesN<32> {
let mut msg: Bytes = domain_separator(env).into();
msg.extend_from_array(&signers_hash.to_array());
msg.extend_from_array(&data_hash.to_array());

// TODO: use an appropriate non tx overlapping prefix
env.crypto().keccak256(&msg)
env.crypto().keccak256(&msg).into()
}

fn update_rotation_timestamp(env: &Env, enforce_rotation_delay: bool) -> Result<(), ContractError> {
let minimum_rotation_delay: u64 = env
.storage()
.instance()
.get(&DataKey::MinimumRotationDelay)
.expect("minimum_rotation_delay not found");

let last_rotation_timestamp: u64 = env
.storage()
.instance()
Expand All @@ -168,7 +168,7 @@ fn update_rotation_timestamp(env: &Env, enforce_rotation_delay: bool) -> Result<

if enforce_rotation_delay {
ensure!(
current_timestamp - last_rotation_timestamp >= minimum_rotation_delay,
current_timestamp - last_rotation_timestamp >= minimum_rotation_delay(env),
ContractError::InsufficientRotationDelay
);
}
Expand All @@ -180,7 +180,7 @@ fn update_rotation_timestamp(env: &Env, enforce_rotation_delay: bool) -> Result<
Ok(())
}

fn validate_signatures(env: &Env, msg_hash: Hash<32>, proof: Proof) -> bool {
fn validate_signatures(env: &Env, msg_hash: BytesN<32>, proof: Proof) -> bool {
let mut total_weight = 0u128;

for ProofSigner {
Expand All @@ -193,7 +193,7 @@ fn validate_signatures(env: &Env, msg_hash: Hash<32>, proof: Proof) -> bool {
{
if let ProofSignature::Signed(signature) = signature {
env.crypto()
.ed25519_verify(&public_key, msg_hash.to_bytes().as_ref(), &signature);
.ed25519_verify(&public_key, msg_hash.as_ref(), &signature);

total_weight = total_weight.checked_add(weight).unwrap();

Expand All @@ -214,7 +214,6 @@ fn validate_signers(env: &Env, weighted_signers: &WeightedSigners) -> Result<(),
ContractError::EmptySigners
);

// TODO: what's the min address/hash?
let mut previous_signer = BytesN::<32>::from_array(env, &[0; 32]);
let mut total_weight = 0u128;

Expand Down
12 changes: 12 additions & 0 deletions contracts/stellar-axelar-gateway/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@ impl AxelarGatewayMessagingInterface for AxelarGateway {

#[contractimpl]
impl AxelarGatewayInterface for AxelarGateway {
fn domain_separator(env: &Env) -> BytesN<32> {
auth::domain_separator(env)
}

fn minimum_rotation_delay(env: &Env) -> u64 {
auth::minimum_rotation_delay(env)
}

fn previous_signers_retention(env: &Env) -> u64 {
auth::previous_signers_retention(env)
}

fn approve_messages(
env: &Env,
messages: Vec<Message>,
Expand Down
11 changes: 10 additions & 1 deletion contracts/stellar-axelar-gateway/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,23 @@ use crate::AxelarGatewayMessagingInterface;
pub trait AxelarGatewayInterface:
AxelarGatewayMessagingInterface + UpgradableInterface + OwnableInterface + OperatableInterface
{
/// Returns the domain separator.
fn domain_separator(env: &Env) -> BytesN<32>;

/// Returns the number of epochs that previous signers are retained for after rotations.
fn previous_signers_retention(env: &Env) -> u64;

/// Returns the minimum delay between rotations.
fn minimum_rotation_delay(env: &Env) -> u64;

/// Approves a collection of messages.
fn approve_messages(
env: &Env,
messages: Vec<Message>,
proof: Proof,
) -> Result<(), ContractError>;

// TODO: add docstring about how bypass_rotation_delay supposed to be used.
/// Rotates the signers.
fn rotate_signers(
env: &Env,
signers: WeightedSigners,
Expand Down
48 changes: 46 additions & 2 deletions contracts/stellar-axelar-gateway/src/tests/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fn initialization_fails_with_empty_signer_set() {

let empty_signer_set = Vec::<WeightedSigners>::new(&env);
let domain_separator: BytesN<32> = BytesN::random(&env);
let previous_signers_retention = randint(0, 10) as u64;
let previous_signers_retention = randint(0, 10);
let minimum_rotation_delay: u64 = 0;
let initial_signers = empty_signer_set;

Expand Down Expand Up @@ -47,6 +47,50 @@ fn validate_proof_fails_with_invalid_signatures() {
client.validate_proof(&random_hash, &proof);
}

#[test]
fn domain_separator_succeeds_with_register() {
let (_, signers, client) = setup_env(randint(0, 10), randint(1, 10));

assert_eq!(client.domain_separator(), signers.domain_separator);
}

#[test]
fn minimum_rotation_delay_succeeds_with_register() {
let env = &Env::default();

let owner = Address::generate(env);
let operator = Address::generate(env);
let signer_set = generate_signers_set(env, randint(1, 10), BytesN::random(env));
let initial_signers = vec![&env, signer_set.signers.clone()];
let minimum_rotation_delay: u64 = randint(0, u64::MAX);

let contract_id = env.register(
AxelarGateway,
(
owner,
operator,
&signer_set.domain_separator,
minimum_rotation_delay,
randint(0, 10),
initial_signers,
),
);
let client = AxelarGatewayClient::new(env, &contract_id);

assert_eq!(client.minimum_rotation_delay(), minimum_rotation_delay);
}

#[test]
fn previous_signers_retention_succeeds_with_register() {
let previous_signers_retention = randint(0, 10);
let (_, _, client) = setup_env(previous_signers_retention, randint(1, 10));

assert_eq!(
client.previous_signers_retention(),
previous_signers_retention
);
}

#[test]
fn validate_proof_fails_with_empty_signatures() {
let (env, signers, client) = setup_env(randint(0, 10), randint(1, 10));
Expand Down Expand Up @@ -348,7 +392,7 @@ fn rotate_signers_fails_with_insufficient_rotation_delay() {
operator,
&signers.domain_separator,
minimum_rotation_delay,
previous_signers_retention as u64,
previous_signers_retention,
initial_signers,
),
);
Expand Down
4 changes: 2 additions & 2 deletions contracts/stellar-axelar-gateway/src/tests/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use crate::testutils::{setup_gateway, TestSignerSet};
use crate::AxelarGatewayClient;

pub fn setup_env<'a>(
previous_signers_retention: u32,
num_signers: u32,
previous_signers_retention: u64,
num_signers: u64,
) -> (Env, TestSignerSet, AxelarGatewayClient<'a>) {
let env = Env::default();
let (signers, client) = setup_gateway(&env, previous_signers_retention, num_signers);
Expand Down
12 changes: 6 additions & 6 deletions contracts/stellar-axelar-gateway/src/testutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ pub struct TestSignerSet {

pub fn setup_gateway<'a>(
env: &Env,
previous_signers_retention: u32,
num_signers: u32,
previous_signers_retention: u64,
num_signers: u64,
) -> (TestSignerSet, AxelarGatewayClient<'a>) {
let owner = Address::generate(env);
let operator = Address::generate(env);
Expand All @@ -38,7 +38,7 @@ pub fn setup_gateway<'a>(
operator,
&signer_set.domain_separator,
minimum_rotation_delay,
previous_signers_retention as u64,
previous_signers_retention,
initial_signers,
),
);
Expand Down Expand Up @@ -79,21 +79,21 @@ pub fn generate_test_message_with_rng(
)
}

pub fn randint(a: u32, b: u32) -> u32 {
pub fn randint(a: u64, b: u64) -> u64 {
rand::thread_rng().gen_range(a..b)
}

pub fn generate_signers_set(
env: &Env,
num_signers: u32,
num_signers: u64,
domain_separator: BytesN<32>,
) -> TestSignerSet {
generate_signers_set_with_rng(env, num_signers, domain_separator, rand::thread_rng())
}

pub fn generate_signers_set_with_rng(
env: &Env,
num_signers: u32,
num_signers: u64,
domain_separator: BytesN<32>,
mut rng: impl Rng + rand::CryptoRng,
) -> TestSignerSet {
Expand Down
2 changes: 1 addition & 1 deletion packages/stellar-axelar-std/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ use soroban_sdk::{contracttype, Address};
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Token {
pub address: Address, // TODO: check if this can be changed to a TokenClient type instead which is richer than Address, or a generic type implementing TokenInterface
pub address: Address,
pub amount: i128,
}

0 comments on commit ca3b486

Please sign in to comment.