Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: gas service #3

Merged
merged 38 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
cdd734d
feat: add workspace for axelar gas service
canhtrinh Feb 26, 2024
b79b41e
feat: add refund impl and set up tests
canhtrinh Feb 26, 2024
5dee6ab
feat: add impl for pay_native_gas_for_contract_call
canhtrinh Feb 27, 2024
e3c403e
feat: implement collect_fees
canhtrinh Mar 1, 2024
b7d0b57
feat: update gas service unit tests
canhtrinh Mar 5, 2024
db1cc0f
chore: remove vec from and add emitted event to collect_fees api
canhtrinh Mar 7, 2024
49747d0
chore: cargo fmt
canhtrinh Mar 7, 2024
145c26b
Update contracts/axelar-gas-service/Cargo.toml
canhtrinh Mar 14, 2024
50cf1c3
Update contracts/axelar-gas-service/Cargo.toml
canhtrinh Mar 14, 2024
32a13ae
Update contracts/axelar-gas-service/src/contract.rs
canhtrinh Mar 14, 2024
8941d2a
Update contracts/axelar-gas-service/src/test.rs
canhtrinh Mar 14, 2024
42b2270
Update contracts/axelar-gas-service/src/error.rs
canhtrinh Mar 14, 2024
85260a1
Update contracts/axelar-gas-service/src/event.rs
canhtrinh Mar 14, 2024
48553de
Update contracts/axelar-gas-service/src/event.rs
canhtrinh Mar 14, 2024
ee25e75
Update contracts/axelar-gas-service/src/event.rs
canhtrinh Mar 14, 2024
4aac186
Update contracts/axelar-gas-service/src/event.rs
canhtrinh Mar 14, 2024
a016bfc
Update contracts/axelar-gas-service/src/interface.rs
canhtrinh Mar 14, 2024
e8735eb
Update contracts/axelar-gas-service/src/lib.rs
canhtrinh Mar 14, 2024
f54e251
chore: merge latest main
canhtrinh Mar 17, 2024
3a15f92
chore: addressing latest comments on gas-service PR
canhtrinh Mar 17, 2024
ac472f4
chore: fmt code
canhtrinh Mar 17, 2024
12e4837
chore: reduce parameter list size in pay_gas_for_contract_call
canhtrinh Mar 17, 2024
f68df95
Merge branch 'main' into feat/gas-service
milapsheth Mar 18, 2024
884d414
Update contracts/axelar-soroban-interfaces/src/axelar_gas_service.rs
canhtrinh Mar 18, 2024
a9184fd
Update contracts/axelar-soroban-interfaces/src/axelar_gas_service.rs
canhtrinh Mar 18, 2024
a2c9bef
Update contracts/axelar-gas-service/src/contract.rs
canhtrinh Mar 18, 2024
628bb19
Update contracts/axelar-soroban-interfaces/src/axelar_gas_service.rs
canhtrinh Mar 18, 2024
29c07ef
Update contracts/axelar-gas-service/src/contract.rs
canhtrinh Mar 18, 2024
4be6a51
Update packages/axelar-soroban-std/src/types.rs
canhtrinh Mar 18, 2024
0a2063a
Update contracts/axelar-soroban-interfaces/src/axelar_gas_service.rs
canhtrinh Mar 18, 2024
b76738b
chore: refactor gas service
canhtrinh Mar 19, 2024
cccc748
chore: update pay_gas_for_contract_call to use tranfer_from
canhtrinh Mar 19, 2024
f9d764a
Update contracts/axelar-gas-service/src/event.rs
canhtrinh Mar 19, 2024
c613025
Update contracts/axelar-gas-service/src/contract.rs
canhtrinh Mar 19, 2024
b6b56ac
Update contracts/axelar-gas-service/src/contract.rs
canhtrinh Mar 19, 2024
5299355
Update contracts/axelar-gas-service/src/test.rs
canhtrinh Mar 19, 2024
8538ef6
Update contracts/axelar-gas-service/src/test.rs
canhtrinh Mar 19, 2024
a8fb7b8
chore: fix tests with variable name change
canhtrinh Mar 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ soroban-sdk = { version = "20.2.0" }
axelar-soroban-interfaces = { version = "^0.1.0", path = "contracts/axelar-soroban-interfaces" }
axelar-soroban-std = { version = "^0.1.0", path = "packages/axelar-soroban-std" }
axelar-auth-verifier = { version = "^0.1.0", path = "contracts/axelar-auth-verifier" }
axelar-gas-service = { version = "^0.1.0", path = "contracts/axelar-gas-service" }
axelar-gateway = { version = "^0.1.0", path = "contracts/axelar-gateway" }

[profile.release]
Expand Down
16 changes: 16 additions & 0 deletions contracts/axelar-gas-service/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "axelar-gas-service"
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
version = "0.1.0"
edition = { workspace = true }

[lib]
crate-type = ["cdylib"]

[dependencies]
axelar-soroban-interfaces = { workspace = true }
axelar-soroban-std = { workspace = true }
soroban-sdk = { workspace = true }

[dev_dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
axelar-soroban-std = { workspace = true, features = ["testutils"] }
120 changes: 120 additions & 0 deletions contracts/axelar-gas-service/src/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use soroban_sdk::{
contract, contractimpl, panic_with_error, token, Address, Bytes, Env, String, U256,
};

use axelar_soroban_std::types::{Hash, TokenDetails};

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

#[contract]
pub struct AxelarGasService;

#[contractimpl]
impl AxelarGasService {
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_gas_for_contract_call(
env: Env,
sender: Address,
destination_chain: String,
destination_address: String,
payload: Bytes,
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
refund_address: Address,
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
token_details: TokenDetails,
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
) {
sender.require_auth();

let TokenDetails { token_addr, amount } = token_details.clone();

if amount == 0 {
panic_with_error!(env, Error::InvalidAmount);
}

token::Client::new(&env, &token_addr).transfer(
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
&sender,
&env.current_contract_address(),
&amount,
);

event::gas_paid_for_contract_call(
&env,
sender,
destination_chain,
destination_address,
payload,
refund_address,
token_details,
);
}

fn collect_fees(env: Env, receiver: Address, token_addr: Address, amount: i128) {
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
let gas_collector: Address = env
.storage()
.instance()
.get(&DataKey::GasCollector)
.unwrap();

gas_collector.require_auth();

if amount == 0 {
panic_with_error!(env, Error::InvalidAmount);
}

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 {
panic_with_error!(env, Error::InsufficientBalance);
}

event::fee_collected(&env, &gas_collector, &token_addr, amount);
}

fn refund(
env: Env,
tx_hash: Hash,
log_index: U256,
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
receiver: Address,
token_addr: Address,
amount: i128,
) {
let gas_collector: Address = env
.storage()
.instance()
.get(&DataKey::GasCollector)
.unwrap();

gas_collector.require_auth();

token::Client::new(&env, &token_addr).transfer(
&env.current_contract_address(),
&receiver,
&amount,
);

event::refunded(&env, tx_hash, log_index, &receiver, &token_addr, 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,
InvalidAmount = 2,
InsufficientBalance = 3,
}
40 changes: 40 additions & 0 deletions contracts/axelar-gas-service/src/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use axelar_soroban_std::types::{Hash, TokenDetails};
use soroban_sdk::{symbol_short, Address, Bytes, Env, String, U256};

pub(crate) fn gas_paid_for_contract_call(
env: &Env,
sender: Address,
destination_chain: String,
destination_address: String,
payload: Bytes,
refund_address: Address,
token_details: TokenDetails,
) {
let topics = (
symbol_short!("gas_paid"),
env.crypto().keccak256(&payload),
sender,
destination_chain,
);
env.events().publish(
topics,
(destination_address, payload, refund_address, token_details),
);
}

pub(crate) fn refunded(
env: &Env,
tx_hash: Hash,
log_index: U256,
receiver: &Address,
token: &Address,
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
amount: i128,
) {
let topics = (symbol_short!("refunded"), tx_hash, log_index, receiver);
env.events().publish(topics, (token, amount));
}

pub(crate) fn fee_collected(env: &Env, receiver: &Address, token_address: &Address, amount: i128) {
let topics = (symbol_short!("collected"), receiver, token_address, amount);
env.events().publish(topics, ());
}
11 changes: 11 additions & 0 deletions contracts/axelar-gas-service/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![no_std]

pub mod contract;
mod error;
mod event;
mod storage_types;

#[cfg(test)]
mod test;

pub use contract::AxelarGasServiceClient;
8 changes: 8 additions & 0 deletions contracts/axelar-gas-service/src/storage_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use soroban_sdk::contracttype;

#[contracttype]
#[derive(Clone, Debug)]
pub enum DataKey {
Initialized,
GasCollector,
}
131 changes: 131 additions & 0 deletions contracts/axelar-gas-service/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#![cfg(test)]
extern crate std;

use axelar_soroban_std::{assert_emitted_event, types::TokenDetails};

use crate::contract::{AxelarGasService, AxelarGasServiceClient};
use soroban_sdk::{
bytes, bytesn, symbol_short,
testutils::Address as _,
token::{StellarAssetClient, TokenClient},
Address, Env, String, U256,
};

fn setup_env<'a>() -> (Env, Address, Address, AxelarGasServiceClient<'a>) {
let env = Env::default();

env.mock_all_auths();

let contract_id = env.register_contract(None, AxelarGasService);

let client = AxelarGasServiceClient::new(&env, &contract_id);
let gas_collector: Address = Address::generate(&env);
client.initialize(&gas_collector);

(env, contract_id, gas_collector, client)
}

#[test]
fn pay_gas_for_contract_call() {
let (env, contract_id, _, client) = setup_env();

let token_addr: Address = env.register_stellar_asset_contract(Address::generate(&env));
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
let sender: Address = Address::generate(&env);
let gas_amount: i128 = 1;
let token_details = TokenDetails {
token_addr: token_addr.clone(),
amount: gas_amount,
};
let refund_address: Address = Address::generate(&env);
let payload = bytes!(&env, 0x1234);
let destination_chain: String = String::from_str(&env, "ethereum");
let destination_address: String =
String::from_str(&env, "0x4EFE356BEDeCC817cb89B4E9b796dB8bC188DC59");

let token_client = TokenClient::new(&env, &token_addr);
StellarAssetClient::new(&env, &token_addr).mint(&sender, &gas_amount);

client.pay_gas_for_contract_call(
&sender,
&destination_chain,
&destination_address,
&payload,
&refund_address,
&token_details,
);

assert_eq!(0, token_client.balance(&sender));
assert_eq!(gas_amount, token_client.balance(&contract_id));

assert_emitted_event(
&env,
-1,
&contract_id,
(
symbol_short!("gas_paid"),
env.crypto().keccak256(&payload),
sender,
destination_chain,
),
(destination_address, payload, refund_address, token_details),
);
}

#[test]
fn collect_fees() {
let (env, contract_id, gas_collector, client) = setup_env();
let token_addr: Address = env.register_stellar_asset_contract(Address::generate(&env));
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
let token_client = TokenClient::new(&env, &token_addr);
let supply: i128 = 1000;
StellarAssetClient::new(&env, &token_addr).mint(&contract_id, &supply);

let refund_amount = 1;

client.collect_fees(&gas_collector, &token_addr, &refund_amount);

assert_eq!(refund_amount, token_client.balance(&gas_collector));
assert_eq!(supply - refund_amount, token_client.balance(&contract_id));

assert_emitted_event(
&env,
-1,
&contract_id,
(
symbol_short!("collected"),
gas_collector,
token_addr,
refund_amount,
),
(),
);
}

#[test]
fn refund() {
let (env, contract_id, _, client) = setup_env();
let token_addr: Address = env.register_stellar_asset_contract(Address::generate(&env));
let token_client = TokenClient::new(&env, &token_addr);
let supply: i128 = 1000;
StellarAssetClient::new(&env, &token_addr).mint(&contract_id, &supply);

let tx_hash = bytesn!(
&env,
0xfded3f55dec47250a52a8c0bb7038e72fa6ffaae33562f77cd2b629ef7fd424d
);
let log_index: U256 = U256::from_u32(&env, 0);
let receiver: Address = Address::generate(&env);
let refund_amount: i128 = 1;

client.refund(&tx_hash, &log_index, &receiver, &token_addr, &refund_amount);

assert_eq!(refund_amount, token_client.balance(&receiver));
assert_eq!(supply - refund_amount, token_client.balance(&contract_id));

assert_emitted_event(
&env,
-1,
&contract_id,
(symbol_short!("refunded"), tx_hash, log_index, receiver),
(token_addr, refund_amount),
);
}
30 changes: 30 additions & 0 deletions contracts/axelar-soroban-interfaces/src/axelar_gas_service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use axelar_soroban_std::types::{Hash, TokenDetails};
use soroban_sdk::{Address, Bytes, Env, String, U256};

/// Interface for the Axelar Gas Service.
// #[contractclient(crate_path = "crate", name = "AxelarGasService")]
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
pub trait AxelarGasServiceInterface {
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
/// 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.
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
fn pay_gas_for_contract_call(
env: Env,
sender: Address,
destination_chain: String,
destination_address: String,
payload: Bytes,
refund_address: Address,
token_details: TokenDetails,
);

/// Allows the gasCollector to collect accumulated fees from the contract.
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
fn collect_fees(env: Env, receiver: Address, token_addr: Address, amounts: i128);
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved

/// 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: Hash,
log_index: U256,
receiver: Address,
token: Address,
canhtrinh marked this conversation as resolved.
Show resolved Hide resolved
amount: i128,
);
}
Loading
Loading