---
# System prepended metadata

title: 版本一

---

# 版本一


## module-xtokens

### 修改跨链转账extrinsic call的流程
orml-trait 新增定义:

```
enum RestrictStatus {
	Pass,
	Prohibit,
	Delay(BlockNumber),
}

trait CallRestrict<AccountId, BlockNumber, Call> {
	fn check_restrict_status(who: &AccountId, call_type: Call) -> RestrictStatus;
	fn new_delay_call(who: &AccountId, call_type: Call, delay_block: BlockNumber) -> DispatchResult;
	fn pass_callback(who: &AccountId, call_type: Call);
}

trait DelayCallHandler<AccountId, BlockNumber, Call> {
	fn pre_delay(who: AccountId, call_type: Call) -> DispatchResult;
	fn pre_execute_delayed(who: &AccountId, call_type: Call) -> DispatchResult;
	fn execute_delayed(who: &AccountId, call_type: Call) -> DispatchResult;
}
```

+ `RestrictStatus::Pass`, 不做限制, 按原本流程继续执行
+ `RestrictStatus::Prohibit`, 禁止该调用, 返回错误
+ `RestrictStatus::Delay(BlockNumber)`, 延迟BlockNumber后执行


orml-xtokens 新增定义:

```
enum XtokensCall {
	TransferMultiAssets(params)
	TransferMultiAssetsWithFee(params)
}
```

CallRestrict 由 module-restrict 实现。 orml-xtokens 需要进行restrict的call， 要在 enum XtokensCall 中进行注册， 并且修改call的执行流程， 用一套通用的流程包住原本的执行逻辑（注意不要影响到系统内部函数的调用）， 以orml-xtokens 的 transfer_multi_assets call为例：

```
fn transfer_multiassets(origin, params) {
	let who = ensure_signed(origin)?;
	
	match restrict::check_restrict_status(who, XtokensCall::TransferMultiAssets(params)) {
		RestrictStatus::Pass => {
			// 执行原本的逻辑
			xtokens::transfer(who, params)?;
			
			// 执行回调函数
			restrict::pass_callback(who, XtokensCall::TransferMultiAssets(params));
		},
		RestrictStatus::Prohibit => {
			return Err;
		},
		RestrictStatus::Delay(delay_block) => {
			restrict:: new_delay_call(who, XtokensCall::TransferMultiAssets(params), delay_block);
		}
	};
	
	Ok(())
}
```



另外会给 () 实现trait CallRestrict<XtokensCall>， 这要如果不集成 restrict 模块， 钩子挂 () 就行， orml-xtokens的 transfer_multiassets 的调用流程就和原来的一致：

```
impl CallRestrict<AccountId, BlockNumber, XtokensCall> for () {
	fn check_restrict_status(who: &AccountId, call_type: XtokensCall) -> RestrictStatus 	{	
		// always pass
		RestrictStatus::Pass
	}
	
	fn new_delay_transfer(who: &AccountId, call_type: XtokensCall, delay_block: BlockNumber) -> DispatchResult {
		unimplement!()
	}
	
	fn pass_callback(who: &AccountId, call_type: XtokensCall) {
		// do nothing
	}
}
``` 


### 实现 DelayCallHandler

orml-xtokens 也需要实现 trait DelayCallHandler ：

```
impl DelayCallHandler<AccountId, BlockNumber, XtokensCall> for Xtokens{
	fn pre_delay(who: &AccountId, call_type: XtokensCall) -> DispatchResult {
		// 获取需要transfer的资产，进行 reserve 处理 
		let assets: Vec<(CurrencyId, Balance)> = get_params(operation);
		
		for (currency_id, amount) in assets.iter() {
			Currencies::reserve(who, currency_id, amount)?;
		}
	}
	
	fn pre_execute_delayed(who: &AccountId, call_type: XtokensCall) -> DispatchResult {
		// 获取需要transfer的资产，进行 unreserve 
		let assets: Vec<(CurrencyId, Balance)> = get_params(delayed_transfer);
		
		for (currency_id, amount) in assets.iter() {
			Currencies::unreserve(who, currency_id, amount)?;
		}
	}
	
	fn execute_delayed(who: &AccountId, call_type: XtokensCall) -> DispatchResult {
		match operation {
			TransferMultiAssets(params) => xtokens.transfer_multiassets(who, params),
			TransferMultiAssetsWithFee(params) => xtokens.transfer_multiassets_with_fee(who, params)
		}
	}
}
```

## module-restrict
新增pallet module-restrict(名称暂定) ， 可以对某些pallet的call进行restrict判断，以及记录和触发执行delayed call。


### 规则配置
module-restrict 新增对orml-xtokens 跨链转账的restrict规则

```
struct XtokensTransferRestriction {
	// 如果配置为true， 始终返回 RestrictStatus::Prohibit
	prohibited: bool, 
	
	// 如果有配置， 当xtoken transfer的资产大与 Balance 时， 返回 RestrictStatus::Delay(BlockNumber)
	large_transfer_delay: Option<(BlockNumber, Balance)>,
	
	// 如果有配置， BlockNumber 为触发因累积量达到上限的delay时间， Balance 为触发累积量延迟的数量
	transfer_accumulation_delay: Option<(BlockNumber, Balance)>,
	
	// transfer累积量
	transfer_cumulative_in_period: Balance
}


// option 1: 针对每条链的每个资产的跨链转账都有独立的规则和统计
XtokensTransferRestrictions: double_map Multilocation, CurrencyId => XtokensTransferRestriction


// option 2: 针对每种资产的跨链转账，不区分目的地
XtokensTransferRestrictions: map CurrencyId => XtokensTransferRestriction
```
option1 和 option2 可以选择一种实现, 理论上也可以两者共存, 限制更加细化但也更复杂。

对于这些规则， 还有 extrinsic call 供 council/公投 调用:

```
fn update_xtokens_transfer_restriction(origin, ...) -> DispatchResult;
```

按照option2， 给module-restrict实现trait CallRestrict:

```
impl CallRestrict<AccountId, BlockNumber, XtokensCall> for Restrict {
	fn check_restrict_status(who: &AccountId, call_type: XtokensCall) -> RestrictStatus {
		let (multilocation, assets): (MultiLocation, Vec<(CurrencyId, Balance)>) = get_params(operation);
		
		let mut prohibited: bool = false;
		let mut delay: Option<BlockNumber> = None;
		
		for (currency_id, amount) in assets {
			let restrictions = XtokensTransferRestrictions::get(currency_id);
			
			// 检查该资产是否已被禁止跨链
			if restrictions.prohibited {
				pause = true;
				break;
			}
			
			// 检查该资产是否触发大额限制
			if let Some(delay_block, large_transfer_delay) = restrictions.large_transfer_delay {
				if amount >= large_transfer_delay {
					// 使用delay更久的
					delay = max(delay, delay_block);
				}
			}
			
			// 检查该资产是否触发累积量限制
			if let Some(delay_block, transfer_accumulation_delay) = restrictions.transfer_accumulation_delay {
				if restrictions.transfer_cumulative_in_period + amount >= large_transfer_delay {
					// 使用delay更久的
					delay = max(delay, delay_block);
				}
			}
		}
		
		match (prohibited, delay) {
			(true, _) => RestrictStatus::Prohibit,
			(false, None) => RestrictStatus::Pass,
			(false, Some(delay_block)) => RestrictStatus::Delay(delay_call_type:block),
		}
	}
	
	fn new_delay_call(who: &AccountId, call_type: XtokensCall, delay_block: BlockNumber) -> DispatchResult {
		push_delayed_call(who, delay_call_type, delay_block)
	}
	
	fn pass_callback(who: &AccountId, call_type: XtokensCall) {
		let assets = get_params(call_type).assets;
		for (currency_id, amount) in assets {
			XtokenTransferRestrictions::mutate(currency_id, |restrcition| {
				if restrcition.transfer_accumulation_delay.is_some() {
					*restrcition.transfer_cumulative_in_period += amount;
				}
			});			
		}
	}
}
``` 

pallet restrict 还配置有 `Const XtokenTransferCumulativeResetPeriod: BlockNumber`, 每过这么多时间，将所有 XtokenTransferRestrict 配置中的 transfer_cumulative_in_period 重置为 0

### delay-queue

定义三个存储, NextDelayIndex 和 DelayEndTime 用于维护 delay queue， DelayedXtokensTransfer 用于维护 xtokens delay transfer的调用内容。

```
enum DelayCallType {
	Xtokens(XtokensCall)
}


NextDelayIndex: value DelayId;

DelayEndTime: double_map BlockNumber, DelayId => ()

DelayedCalls: map DelayId => (AccountId, DelayCallType, BlockNumber);

```

需要定义一些内部函数：

```
fn push_delayed_call(who: &AccountId, delay_call_type: DelayCallType, delay_block: BlockNumber) -> DispatchResult {
	// 先执行 Xtokens的pre_delay
	xtokens::pre_delay(who, call_type)?;
	
	// 获取 NextDelayIndex， 记录相应信息至 DelayEndTime， DelayedCalls
}

fn pop_delayed_call(delay_id: DelayId) -> DispatchResult {
	// 获取delayed call的信息
	let (who, delay_call_type, _) = DelayedCalls::get(delay_id)?;
	
	// 先执行 Xtokens的 pre_execute_delayed
	xtokens::pre_execute_delayed(who, delay_call_type)?;
	
	// 再执行 Xtokens 的 execute_delayed
	xtokens::execute_delayed(who, delay_call_type)?;
	
	// 删除掉 delay_id 的相关存储
}

fn on_initialized(n: BlockNumber) {
	// 遍历出当前已经到期的delay_id 
	for delay_id in DelayEndTime::drain_prefix(n) {
		match pop_delayed_call(delay_id) {
			Ok(_) => // trigger event,
			Err(_) => // trigger event,
		}
	}
}
```

另外，存在 pop_delayed_call 失败的情况（比如用户reserved的资产被其他地方错误的多unreserve了，导致执行 pre_execute_delayed 时失败）， 失败的 DelayedCalls 会留存， 可以通过cancel_delayed_call给用户进行操作

```
fn cancel_delayed_call(origin, delay_id) -> DispatchResult {
	let caller = ensure_signed(origin)?;
	
	let (who, delay_call_type, expired_block) = DelayedCalls::take(delay_id)?;
	
	ensure!(who == caller);
	
	// 如果不允许用户主动取消未到delay时间的delayed call, 只能取消delay 执行失败的， 可以有如下检查：
	// ensure!(expired_block < System::current_block());
	
	xtokens::pre_execute_delayed(who, delay_call_type)?;

	DelayEndTime::take(who, delay_id)?;
}
```

### 特殊权限
提供一些extrinsic call供 council/公投调用， 进行延迟/加速 delayed call

```
fn change_delayed_call(origin, delay_id: DelayId, when: DispatchTime) {
	// 验证权限， 修改DelayedCalls 和 DelayEndTime
}
```
                                                      
## 额外需求（from Bryan）
1. 限制算法可以考虑用token bucket，就是每X block 增加 Y 限额，直到Z 上限， 直接配置数量改为配置公式和常量 (如果限制量不需要做到经常调整， 也可以不用这样， 还是等需要的是走council/公投去配置直接量)
2. 转账方法可能要有个参数让用户选择可以接受最多少delay，如果传0的话就是要么马上转账要么交易失败. (但这个需求需要改动xtokens 原来的call的定义来添加这个参数)
3. 我们要用方法可以暂停整个delay queue的处理。(可以做到，需要修改下sheduler机制， 现在是类似auction的简单schedule)


## 其他
1. apps，polkawallet等前端会有大的改动， subscan对delayed跨链转账的交易的展示不一定支持得很好。 


# 版本2
版本1的设计，要改动限制模块的实现，和功能模块是高度耦合的，delay的实现也会复杂，还会涉及到用户资金的变动。 
                                                        
1. 不支持delay的限制， 当数量达到限制后， 直接禁止掉。 
2. 不用需要orml-xtokens， module-restrict 不要delay相关的实现， 不挂钩子。
3. 还可以加入白名单功能， 对于白名单可以始终返回 Pass， 不做限制。
4. xtokens在通过transfer_multiassets 同事转账多个assets时， 如果其中一种资产触发Prohibit/Delay， 整笔交易都会Prohibit/Delay， 不能拆分， 也满足交易的原子性和符合交易意图。

## module-restrict

配置简化为：

```
struct XtokensTransferRestriction {
	// Balance 为累积量上限
	transfer_accumulation_period_limit: Option<Balance>,
	
	// transfer 累积量
	transfer_cumulative_in_period: Balance
}


// option 1: 针对每条链的每个资产的跨链转账都有独立的规则和统计
XtokensTransferRestrictions: double_map Multilocation, CurrencyId => XtokensTransferRestriction


// option 2: 针对每种资产的跨链转账，不区分目的地
XtokensTransferRestrictions: map CurrencyId => XtokensTransferRestriction
```

同样的 option1 option2 可以取其一， 同时存在也可。

对call的限制判断通过实现 trait SignedExtension 来做到：

```
pub struct XtokenCallRestrict<T>(PhantomData<T>);

impl SignedExtension for ChargeTransactionPayment<T> {
	// 在交易dispatch前做检查， 有错误的交易不会被打包， 前端在发送交易后就会得到错误提示
	fn pre_dispatch(call: &CallOf<T>, ...) -> Result<Self::Pre, TransactionValidityError> {
		// 判断call是否属于orml-xtokens的跨链转账调用
		match call {
			xtokens::Transfer | xtokens::TransferMultiAssets | xtokens::TransferMultiAssetsWithFee => {
				// 解析跨链转账的量
				let (multilocation, assets): (MultiLocation, Vec<(CurrencyId, Balance)>) = get_params(call);
		
				for (currency_id, amount) in assets {
					let restrictions = XtokensTransferRestrictions::get(currency_id);
					
					// 检查该资产是否触发累积量限制
					if let Some(limit) = restrictions.transfer_accumulation_period_limit {
						if restrictions.transfer_cumulative_in_period + amount >= limit {
							return TransactionValidityError;
						}
					}
				}
			},
			_ => // do nothing,
		}
	}
	
	// 打包交易后执行
	fn post_dispatch(pre: Option<Self::Pre>, result: &DispatchResult, ...) -> Result<(), TransactionValidityError> {
		// 判断call是否属于orml-xtokens的跨链转账调用
		match call {
			xtokens::Transfer | xtokens::TransferMultiAssets | xtokens::TransferMultiAssetsWithFee => {
				//	判断 call 是否执行成功 
				if result.is_ok() {
					let assets = get_params(pre.call).assets;
					
					for (currency_id, amount) in assets {
						XtokenTransferRestrictions::mutate(currency_id, |restrcition| {
							if restrcition. restrictions.transfer_accumulation_period_limit.is_some() {
								// 累积成功transfer的数量
								*restrcition.transfer_cumulative_in_period += amount;
							}
						});			
					}
				}
				
			},
			_ => // do nothing,
		}
	}
}
```

这样如果达到上限， 交易不会被打包，发送给节点时就会收到报错信息。 