金融

此模块包含金融系统的原语。

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 计划开始以来经过的时间成正比。

用法

合约必须将 VestingComponentOwnableComponent 作为依赖项集成。合约的构造函数应该初始化这两个组件。核心 vesting 参数,例如 beneficiarystartdurationcliff_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);
}