Skip to content

Commit

Permalink
feat: implement collect_fees
Browse files Browse the repository at this point in the history
chore: add back contract_id as output to the setup_emv test

chore: update tests to pass in correct token address into refund

chore: WIP - adding auth for refund methods and tests for emitted events

chore: unwrapping index value
  • Loading branch information
canhtrinh committed Mar 5, 2024
1 parent 5dee6ab commit e3c403e
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 64 deletions.
128 changes: 100 additions & 28 deletions contracts/axelar-gas-service/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,122 @@
use soroban_sdk::token::TokenClient;
use soroban_sdk::{contract, contractimpl, Address, Bytes, BytesN, Env, String, U256};
use soroban_sdk::{contract, contractimpl, token, Address, Bytes, BytesN, Env, String, Vec, U256};

use crate::interface::AxelarGasServiceInterface;
use crate::event;
use crate::storage_types::DataKey;
use crate::{error::Error, event};

#[contract]
pub struct AxelarGasService;

#[contractimpl]
impl AxelarGasService {

pub fn initialize(env: Env) {
if env.storage().instance().get(&DataKey::Initialized).unwrap_or(false) {
pub fn initialize(env: Env, gas_collector: Address) {
if env
.storage()
.instance()
.get(&DataKey::Initialized)
.unwrap_or(false)
{
panic!("Already initialized");
}

env.storage().instance().set(&DataKey::Initialized, &true);

env.storage()
.instance()
.set(&DataKey::GasCollector, &gas_collector);
}
}

#[contractimpl]
impl AxelarGasServiceInterface for AxelarGasService {

fn pay_native_gas_for_contract_call(env: Env, sender: Address, destination_chain: String, destination_address: String, payload: Bytes, refund_address: Address) {
event::native_gas_paid_for_contract_call(&env, sender, destination_chain, destination_address, payload, refund_address)
}

fn collect_fees() {
fn pay_native_gas_for_contract_call(
env: Env,
sender: Address,
destination_chain: String,
destination_address: String,
payload: Bytes,
refund_address: Address,
) {
event::native_gas_paid_for_contract_call(
&env,
sender,
destination_chain,
destination_address,
payload,
refund_address,
)
}


fn refund(env: Env, tx_hash: BytesN<32>, log_index: U256, receiver: Address, token_addr: Address, amount: i128) {

//TODOs
// 1. need modifier function to ensure only callable by collector
// 2. confirm whether we want to generalize for non-native tokens

event::refunded(&env, tx_hash, log_index, receiver, token_addr, amount);

let token = TokenClient::new(&env, &token_addr);

// 3. send tokens to receiver. test this
token.transfer(&env.current_contract_address(), &receiver, &amount)

fn collect_fees(
env: Env,
receiver: Address,
token_addresses: Vec<Address>,
amounts: Vec<i128>,
) -> Result<(), Error> {
//TODO confirm this is analogous to onlyGasCollector in Solidity
let gas_collector: Address = env
.storage()
.instance()
.get(&DataKey::GasCollector)
.unwrap();

gas_collector.require_auth();

//TODO: sanity check address zero

let token_addr_length = token_addresses.len();

if token_addr_length != amounts.len() {
return Err(Error::InvalidAmounts);
}

for i in 0..token_addr_length {
let amount = amounts.get(i).unwrap();

let token_addr = token_addresses
.get(i).unwrap();

if amount == 0 {
return Err(Error::InvalidAmounts);
}

let token_client = token::Client::new(&env, &token_addr);

let contract_token_balance = token_client.balance(&env.current_contract_address());

if contract_token_balance >= amount {
token_client.transfer(&env.current_contract_address(), &receiver, &amount)
} else {
return Err(Error::InsufficientBalance);
}
}

Ok(())
}


}
fn refund(
env: Env,
tx_hash: BytesN<32>,
log_index: U256,
receiver: Address,
token_addr: Address,
amount: i128,
) {
//TODO confirm this is analogous to onlyGasCollector in Solidity
let gas_collector: Address = env
.storage()
.instance()
.get(&DataKey::GasCollector)
.unwrap();

gas_collector.require_auth();

event::refunded(&env, tx_hash, log_index, &receiver, &token_addr, amount);

token::Client::new(&env, &token_addr).transfer(
&env.current_contract_address(),
&receiver,
&amount,
);
}
}
10 changes: 10 additions & 0 deletions contracts/axelar-gas-service/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use soroban_sdk::contracterror;

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Error {
InvalidAddress = 1,
InvalidAmounts = 2,
InsufficientBalance = 3,
}
31 changes: 27 additions & 4 deletions contracts/axelar-gas-service/src/event.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
use soroban_sdk::{symbol_short, Address, Bytes, BytesN, Env, String, U256};

pub(crate) fn refunded(env: &Env, tx_hash: BytesN<32>, log_index: U256, receiver: Address, token: Address, amount: i128) {
pub(crate) fn refunded(
env: &Env,
tx_hash: BytesN<32>,
log_index: U256,
receiver: &Address,
token: &Address,
amount: i128,
) {
let topics = (symbol_short!("refunded"), tx_hash, log_index);
env.events().publish(topics, (receiver, token, amount));
}

pub(crate) fn native_gas_paid_for_contract_call(env: &Env, sender: Address, destination_chain: String, destination_address: String, payload: Bytes, refund_address: Address) {
pub(crate) fn native_gas_paid_for_contract_call(
env: &Env,
sender: Address,
destination_chain: String,
destination_address: String,
payload: Bytes,
refund_address: Address,
) {
let topics = (symbol_short!("cc_g_paid"),);
env.events().publish(topics, (sender, destination_chain, destination_address, env.crypto().keccak256(&payload), refund_address));
}
env.events().publish(
topics,
(
sender,
destination_chain,
destination_address,
env.crypto().keccak256(&payload),
refund_address,
),
);
}
36 changes: 27 additions & 9 deletions contracts/axelar-gas-service/src/interface.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
use soroban_sdk::{Address, Bytes, BytesN, Env, String, U256};
use crate::error::Error;
use soroban_sdk::{Address, Bytes, BytesN, Env, String, Vec, U256};
/// Interface for the Axelar Gas Service.
// #[contractclient(crate_path = "crate", name = "AxelarGasService")]
pub trait AxelarGasServiceInterface {

/// Pay for gas using native currency for a contract call on a destination chain. This function is called on the source chain before calling the gateway to execute a remote contract.
fn pay_native_gas_for_contract_call( env: Env, sender: Address, destination_chain: String, destination_address: String, payload: Bytes, refund_address: Address);

fn pay_native_gas_for_contract_call(
env: Env,
sender: Address,
destination_chain: String,
destination_address: String,
payload: Bytes,
refund_address: Address,
);

/// Allows the gasCollector to collect accumulated fees from the contract.
fn collect_fees();

/// Refunds gas payment to the receiver in relation to a specific cross-chain transaction. Only callable by the gasCollector.
fn refund( env: Env, tx_hash: BytesN<32>, log_index: U256, receiver: Address, token: Address, amount: i128);
fn collect_fees(
env: Env,
receiver: Address,
token_addr: Vec<Address>,
amounts: Vec<i128>,
) -> Result<(), Error>;

}
/// Refunds gas payment to the receiver in relation to a specific cross-chain transaction. Only callable by the gasCollector.
fn refund(
env: Env,
tx_hash: BytesN<32>,
log_index: U256,
receiver: Address,
token: Address,
amount: i128,
);
}
7 changes: 4 additions & 3 deletions contracts/axelar-gas-service/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#![no_std]

pub mod interface;
mod types;
pub mod contract;
mod error;
mod event;
pub mod interface;
mod storage_types;
pub mod contract;
mod types;

#[cfg(test)]
mod test;
Expand Down
1 change: 1 addition & 0 deletions contracts/axelar-gas-service/src/storage_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ use soroban_sdk::contracttype;
#[derive(Clone, Debug)]
pub enum DataKey {
Initialized,
GasCollector,
}
Loading

0 comments on commit e3c403e

Please sign in to comment.