Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"contrib",
"multisig-wallet",
"multisig_transfer",
"./opsce",
"opsce",
]

Expand Down
2 changes: 2 additions & 0 deletions contracts/assetsup/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub enum Error {
InvalidTokenSupply = 12,
InvalidTokenDecimals = 13,
InsufficientBalance = 14,
MaxSupplyExceeded = 15,
InvalidTokenTransfer = 16,
InsufficientLockedTokens = 15,
TokensAreLocked = 16,
TransferRestrictionFailed = 17,
Expand Down
2 changes: 2 additions & 0 deletions contracts/assetsup/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ mod initialization;
mod detokenization;
mod dividends;
mod tokenization;
#[path = "../../../opsce/src/tokenization_tests.rs"]
mod tokenization_tests;
mod transfer_restrictions;
mod voting;

Expand Down
16 changes: 13 additions & 3 deletions contracts/assetsup/src/tokenization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,15 @@ pub fn mint_tokens(
return Err(Error::Unauthorized);
}

// Update total supply
tokenized_asset.total_supply += amount;
tokenized_asset.tokens_in_circulation += amount;
let new_total_supply = tokenized_asset
.total_supply
.checked_add(amount)
.ok_or(Error::MaxSupplyExceeded)?;
tokenized_asset.total_supply = new_total_supply;
tokenized_asset.tokens_in_circulation = tokenized_asset
.tokens_in_circulation
.checked_add(amount)
.ok_or(Error::MaxSupplyExceeded)?;

// Update tokenizer's ownership
let holder_key = TokenDataKey::TokenHolder(asset_id, minter.clone());
Expand Down Expand Up @@ -244,6 +250,10 @@ pub fn transfer_tokens(
let key = TokenDataKey::TokenizedAsset(asset_id);
let tokenized_asset: TokenizedAsset = store.get(&key).ok_or(Error::AssetNotTokenized)?;

if from == to {
return Err(Error::InvalidTokenTransfer);
}

// Check if from address has locked tokens
let lock_key = TokenDataKey::TokenLockedUntil(asset_id, from.clone());
if let Some(lock_time) = store.get::<_, u64>(&lock_key) {
Expand Down
1 change: 1 addition & 0 deletions contracts/multisig_transfer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ doctest = false

[dependencies]
soroban-sdk = { workspace = true }
opsce = { path = "../opsce" }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
4 changes: 3 additions & 1 deletion contracts/multisig_transfer/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ pub enum MultiSigError {
NotEnoughApprovals = 16,
ExecuteTooEarly = 17,

RegistryCallFailed = 18,
AssetsUpContractNotConfigured = 18,
AssetTransferFailed = 19,
RegistryCallFailed = 20,
}
26 changes: 26 additions & 0 deletions contracts/multisig_transfer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod utils;

use approvals::*;
use errors::MultiSigError;
use opsce::AssetsUpClient;
use types::{ApprovalRule, RequestStatus, TransferRequest};

#[contract]
Expand Down Expand Up @@ -49,6 +50,16 @@ impl MultiSigTransferContract {
Ok(())
}

pub fn configure_assetsup_contract(
e: Env,
caller: Address,
assetsup_contract_id: Address,
) -> Result<(), MultiSigError> {
utils::require_admin(&e, &caller)?;
storage::set_assetsup_contract(&e, &assetsup_contract_id);
Ok(())
}

// ----------------------------
// Create transfer request
// ----------------------------
Expand All @@ -58,6 +69,8 @@ impl MultiSigTransferContract {
caller: Address,
asset_id: BytesN<32>,
asset_category: BytesN<32>,
token_id: u64,
amount: i128,
new_owner: Address,
notes_hash: BytesN<32>,
expires_at: u64,
Expand Down Expand Up @@ -104,6 +117,8 @@ impl MultiSigTransferContract {
request_id,
asset_id: asset_id.clone(),
asset_category: asset_category.clone(),
token_id,
amount,
current_owner: caller.clone(), // placeholder until registry owner wired
new_owner: new_owner.clone(),
initiator: caller.clone(),
Expand Down Expand Up @@ -267,6 +282,14 @@ impl MultiSigTransferContract {
}
}

let assetsup_contract = storage::get_assetsup_contract(&e)
.ok_or(MultiSigError::AssetsUpContractNotConfigured)?;

let assetsup_client = AssetsUpClient::new(&e, &assetsup_contract);
assetsup_client
.transfer_tokens(req.token_id, req.current_owner.clone(), req.new_owner.clone(), req.amount)
.map_err(|_| MultiSigError::AssetTransferFailed)?;

// registry transfer ownership
registry::transfer_owner(&e, &registry_addr, &req.asset_id, &req.new_owner)?;

Expand Down Expand Up @@ -361,3 +384,6 @@ impl MultiSigTransferContract {
Ok(rule.approvers)
}
}

#[cfg(test)]
mod tests;
11 changes: 11 additions & 0 deletions contracts/multisig_transfer/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::types::{ApprovalRule, TransferRequest};
pub enum DataKey {
Admin, // Address
AssetRegistry, // Address
AssetsUpContract, // Address
NextRequestId, // u64
Requests, // Map<u64, TransferRequest>
Rules, // Map<BytesN<32>, ApprovalRule>
Expand Down Expand Up @@ -34,6 +35,16 @@ pub fn set_registry(e: &Env, registry: &Address) {
.set(&DataKey::AssetRegistry, registry);
}

pub fn get_assetsup_contract(e: &Env) -> Option<Address> {
e.storage().persistent().get(&DataKey::AssetsUpContract)
}

pub fn set_assetsup_contract(e: &Env, contract_id: &Address) {
e.storage()
.persistent()
.set(&DataKey::AssetsUpContract, contract_id);
}

pub fn next_request_id(e: &Env) -> u64 {
e.storage()
.persistent()
Expand Down
139 changes: 139 additions & 0 deletions contracts/multisig_transfer/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use super::*;
use soroban_sdk::{contract, contracterror, contractimpl, symbol_short, testutils::Address as _, Address, BytesN, Env, Vec};

#[contract]
pub struct FakeAssetsUpContract;

#[contractimpl]
impl FakeAssetsUpContract {
pub fn transfer_tokens(
env: Env,
asset_id: u64,
from: Address,
to: Address,
amount: i128,
) -> Result<(), FakeAssetsUpError> {
from.require_auth();
env.events().publish((symbol_short!("asset_xfer"),), (asset_id, from.clone(), to, amount));
Ok(())
}
}

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FakeAssetsUpError {
TransferFailed = 1,
}

#[contract]
pub struct FailingAssetsUpContract;

#[contractimpl]
impl FailingAssetsUpContract {
pub fn transfer_tokens(
_env: Env,
_asset_id: u64,
_from: Address,
_to: Address,
_amount: i128,
) -> Result<(), FakeAssetsUpError> {
Err(FakeAssetsUpError::TransferFailed)
}
}

fn setup_multisig_and_contracts(env: &Env) -> (MultisigTransferContractClient<'_>, Address) {
env.mock_all_auths();
let contract_id = env.register(MultiSigTransferContract, ());
let client = MultisigTransferContractClient::new(env, &contract_id);
let admin = Address::generate(env);
let asset_registry = Address::generate(env);

client.initialize(&admin, &asset_registry);
(client, admin)
}

#[test]
fn test_execute_transfer_invokes_assetsup_transfer_tokens() {
let env = Env::default();
let (client, admin) = setup_multisig_and_contracts(&env);
let fake_assetsup_id = env.register(FakeAssetsUpContract, ());

client.configure_assetsup_contract(&admin, &fake_assetsup_id);
let owner = Address::generate(&env);
let new_owner = Address::generate(&env);
let asset_id = BytesN::from_array(&env, &[1u8; 32]);
let asset_category = BytesN::from_array(&env, &[2u8; 32]);
let notes_hash = BytesN::from_array(&env, &[3u8; 32]);
let token_id = 1u64;
let amount = 1000i128;

let rule = ApprovalRule {
category: asset_category.clone(),
required_approvals: 0,
approvers: Vec::new(&env),
approval_timeout_secs: 3600,
auto_approve: true,
priority: 0,
};
client.configure_approval_rule(&admin, rule);

let request_id = client
.create_transfer_request(
&owner,
&asset_id,
&asset_category,
&token_id,
&amount,
&new_owner,
&notes_hash,
&(env.ledger().timestamp() + 3600),
&None,
);

client.execute_transfer(&owner, &request_id);

let executed_request = client.get_request(&request_id).unwrap();
assert_eq!(executed_request.status, RequestStatus::Executed);
}

#[test]
fn test_execute_transfer_reverts_when_assetsup_transfer_fails() {
let env = Env::default();
let (client, admin) = setup_multisig_and_contracts(&env);
let fake_assetsup_id = env.register(FailingAssetsUpContract, ());

client.configure_assetsup_contract(&admin, &fake_assetsup_id);
let owner = Address::generate(&env);
let new_owner = Address::generate(&env);
let asset_id = BytesN::from_array(&env, &[4u8; 32]);
let asset_category = BytesN::from_array(&env, &[5u8; 32]);
let notes_hash = BytesN::from_array(&env, &[6u8; 32]);
let token_id = 1u64;
let amount = 500i128;

let rule = ApprovalRule {
category: asset_category.clone(),
required_approvals: 0,
approvers: Vec::new(&env),
approval_timeout_secs: 3600,
auto_approve: true,
priority: 0,
};
client.configure_approval_rule(&admin, rule);

let request_id = client
.create_transfer_request(
&owner,
&asset_id,
&asset_category,
&token_id,
&amount,
&new_owner,
&notes_hash,
&(env.ledger().timestamp() + 3600),
&None,
);

let execution_result = client.execute_transfer(&owner, &request_id);
assert!(execution_result.is_err());
}
2 changes: 2 additions & 0 deletions contracts/multisig_transfer/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub struct TransferRequest {

pub asset_id: BytesN<32>,
pub asset_category: BytesN<32>,
pub token_id: u64,
pub amount: i128,

pub current_owner: Address,
pub new_owner: Address,
Expand Down
14 changes: 14 additions & 0 deletions contracts/opsce/src/cross_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use soroban_sdk::{contractclient, Address, RawVal};

#[contractclient]
pub struct AssetsUpClient;

impl AssetsUpClient {
pub fn transfer_tokens(
&self,
asset_id: u64,
from: Address,
to: Address,
amount: i128,
) -> Result<(), RawVal>;
}
3 changes: 3 additions & 0 deletions contracts/opsce/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#![no_std]

pub mod cross_contract;

pub use cross_contract::AssetsUpClient;
use soroban_sdk::{contract, contractimpl, Address, BytesN, Env, String, Vec};

pub mod error;
Expand Down
Loading
Loading