金融
此模块包含金融系统的原语。
Vesting 组件
VestingComponent 管理 ERC-20 代币根据预定义的 vesting 计划逐步释放给指定的受益人。实现合约必须实现 OwnableComponent,其中合约所有者被视为 vesting 的受益人。这种结构允许合约和 vested 代币的所有权被分配和转移。
任何转移到此合约的资产都将遵循 vesting 计划,就像它们从 vesting 周期开始就被锁定一样。因此,如果 vesting 已经开始,新转移的代币的一部分可能会立即变为可释放的。 |
通过将持续时间设置为 0,可以配置此合约的行为类似于资产时间锁,为受益人持有代币直到指定日期。 |
Vesting 计划
VestingSchedule trait 定义了基于给定时间戳计算 vested 金额的逻辑。 这个逻辑不是 VestingComponent 的一部分,因此任何实现 VestingComponent 的合约都必须提供其自己的 VestingSchedule trait 的实现。
有一个现成的 VestingSchedule trait 实现可用,名为 LinearVestingSchedule。 它通过返回 0 vested 金额直到 cliff 结束来合并一个 cliff 周期。 在 cliff 之后,vested 金额被计算为与自 vesting 计划开始以来经过的时间成正比。 |
用法
合约必须将 VestingComponent 和 OwnableComponent 作为依赖项集成。合约的构造函数应该初始化这两个组件。核心 vesting 参数,例如 beneficiary
、start
、duration
和 cliff_duration
,作为参数传递给构造函数,并在部署时设置。
实现合约必须提供 VestingSchedule trait 的实现。这可以通过导入现成的 LinearVestingSchedule 实现或定义自定义实现来实现。
这是一个简单的 vesting 钱包合约的示例,带有 LinearVestingSchedule,其中 vested 金额被计算为与自 vesting 周期开始以来经过的时间成正比。
#[starknet::contract]
mod LinearVestingWallet {
use openzeppelin_access::ownable::OwnableComponent;
use openzeppelin_finance::vesting::{VestingComponent, LinearVestingSchedule};
use starknet::ContractAddress;
component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
component!(path: VestingComponent, storage: vesting, event: VestingEvent);
#[abi(embed_v0)]
impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl<ContractState>;
impl OwnableInternalImpl = OwnableComponent::InternalImpl<ContractState>;
#[abi(embed_v0)]
impl VestingImpl = VestingComponent::VestingImpl<ContractState>;
impl VestingInternalImpl = VestingComponent::InternalImpl<ContractState>;
#[storage]
struct Storage {
#[substorage(v0)]
ownable: OwnableComponent::Storage,
#[substorage(v0)]
vesting: VestingComponent::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
OwnableEvent: OwnableComponent::Event,
#[flat]
VestingEvent: VestingComponent::Event
}
#[constructor]
fn constructor(
ref self: ContractState,
beneficiary: ContractAddress,
start: u64,
duration: u64,
cliff_duration: u64
) {
self.ownable.initializer(beneficiary);
self.vesting.initializer(start, duration, cliff_duration);
}
}
vesting 计划通常遵循自定义公式。在这种情况下,VestingSchedule trait 非常有用。为了支持自定义 vesting 计划,合约必须提供基于所需公式的 calculate_vested_amount 函数的实现。
当使用自定义 VestingSchedule 实现时,必须从导入中排除 LinearVestingSchedule。 |
如果计算需要额外的参数,这些参数存储在合约的存储中,你可以使用 self.get_contract() 访问它们。
|
这是一个带有自定义 VestingSchedule 实现的 vesting 钱包合约的示例,其中代币分几个步骤 vested。
#[starknet::contract]
mod StepsVestingWallet {
use openzeppelin_access::ownable::OwnableComponent;
use openzeppelin_finance::vesting::VestingComponent::VestingScheduleTrait;
use openzeppelin_finance::vesting::VestingComponent;
use starknet::ContractAddress;
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
component!(path: VestingComponent, storage: vesting, event: VestingEvent);
#[abi(embed_v0)]
impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl<ContractState>;
impl OwnableInternalImpl = OwnableComponent::InternalImpl<ContractState>;
#[abi(embed_v0)]
impl VestingImpl = VestingComponent::VestingImpl<ContractState>;
impl VestingInternalImpl = VestingComponent::InternalImpl<ContractState>;
#[storage]
struct Storage {
total_steps: u64,
#[substorage(v0)]
ownable: OwnableComponent::Storage,
#[substorage(v0)]
vesting: VestingComponent::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
OwnableEvent: OwnableComponent::Event,
#[flat]
VestingEvent: VestingComponent::Event
}
#[constructor]
fn constructor(
ref self: ContractState,
total_steps: u64,
beneficiary: ContractAddress,
start: u64,
duration: u64,
cliff: u64,
) {
self.total_steps.write(total_steps);
self.ownable.initializer(beneficiary);
self.vesting.initializer(start, duration, cliff);
}
impl VestingSchedule of VestingScheduleTrait<ContractState> {
fn calculate_vested_amount(
self: @VestingComponent::ComponentState<ContractState>,
token: ContractAddress,
total_allocation: u256,
timestamp: u64,
start: u64,
duration: u64,
cliff: u64,
) -> u256 {
if timestamp < cliff {
0
} else if timestamp >= start + duration {
total_allocation
} else {
let total_steps = self.get_contract().total_steps.read();
let vested_per_step = total_allocation / total_steps.into();
let step_duration = duration / total_steps;
let current_step = (timestamp - start) / step_duration;
let vested_amount = vested_per_step * current_step.into();
vested_amount
}
}
}
}
接口
这是实现 vesting 功能的标准合约的完整接口:
#[starknet::interface]
pub trait VestingABI<TState> {
// IVesting
fn start(self: @TState) -> u64;
fn cliff(self: @TState) -> u64;
fn duration(self: @TState) -> u64;
fn end(self: @TState) -> u64;
fn released(self: @TState, token: ContractAddress) -> u256;
fn releasable(self: @TState, token: ContractAddress) -> u256;
fn vested_amount(self: @TState, token: ContractAddress, timestamp: u64) -> u256;
fn release(ref self: TState, token: ContractAddress) -> u256;
// IOwnable
fn owner(self: @TState) -> ContractAddress;
fn transfer_ownership(ref self: TState, new_owner: ContractAddress);
fn renounce_ownership(ref self: TState);
// IOwnableCamelOnly
fn transferOwnership(ref self: TState, newOwner: ContractAddress);
fn renounceOwnership(ref self: TState);
}