Skip to content

Commit

Permalink
Add docs for migration (#23)
Browse files Browse the repository at this point in the history
* Add documentation for migration api

* Add migration description to requirements doc

* Fix lint

* Fix unit tests

* Fix unit tests

* Update artifact
  • Loading branch information
vasyafromrussia authored Sep 21, 2023
1 parent c9dcebf commit c3f13e6
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 22 deletions.
46 changes: 40 additions & 6 deletions contract/src/ft_receiver.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use near_contract_standards::fungible_token::receiver::FungibleTokenReceiver;
use near_sdk::{
json_types::U128,
require,
serde::{Deserialize, Serialize},
serde_json, PromiseOrValue,
};
Expand All @@ -18,7 +19,7 @@ pub enum FtMessage {
/// Represents a request to create a new jar for a corresponding product.
Stake(StakeMessage),

/// Reserved for internal service use; will be removed shortly after release.
/// Represents a request to create DeFi Jars from provided CeFi Jars.
Migrate(Vec<CeFiJar>),

/// Represents a request to refill (top up) an existing jar using its `JarIndex`.
Expand Down Expand Up @@ -52,6 +53,8 @@ impl FungibleTokenReceiver for Contract {
self.create_jar(receiver_id, message.ticket, amount, message.signature);
}
FtMessage::Migrate(jars) => {
require!(sender_id == self.manager, "Migration can be performed only by admin");

self.migrate_jars(jars, amount);
}
FtMessage::TopUp(jar_index) => {
Expand Down Expand Up @@ -200,7 +203,7 @@ mod tests {

let reference_jar = Jar::generate(0, &alice, &reference_product.id).principal(500);

let mut context = Context::new(admin.clone())
let mut context = Context::new(admin)
.with_products(&[reference_product])
.with_jars(&[reference_jar.clone()]);

Expand All @@ -226,7 +229,7 @@ mod tests {
let initial_jar_principal = 100_000;
let reference_jar = Jar::generate(0, &alice, &reference_product.id).principal(initial_jar_principal);

let mut context = Context::new(admin.clone())
let mut context = Context::new(admin)
.with_products(&[reference_product])
.with_jars(&[reference_jar.clone()]);

Expand Down Expand Up @@ -255,8 +258,8 @@ mod tests {
let reference_product = Product::generate("product").enabled(true).cap(0, 1_000_000);
let reference_restakable_product = Product::generate("restakable_product").enabled(true).cap(0, 1_000_000);

let mut context =
Context::new(admin).with_products(&[reference_product.clone(), reference_restakable_product.clone()]);
let mut context = Context::new(admin.clone())
.with_products(&[reference_product.clone(), reference_restakable_product.clone()]);

let amount_alice = 100;
let amount_bob = 200;
Expand All @@ -283,7 +286,7 @@ mod tests {
context.switch_account_to_ft_contract_account();
context
.contract
.ft_on_transfer(alice.clone(), U128(amount_alice + amount_bob), msg.to_string());
.ft_on_transfer(admin, U128(amount_alice + amount_bob), msg.to_string());

let alice_jars = context.contract.get_jars_for_account(alice);
assert_eq!(alice_jars.len(), 1);
Expand All @@ -294,6 +297,37 @@ mod tests {
assert_eq!(bob_jars.first().unwrap().principal.0, amount_bob);
}

#[test]
#[should_panic(expected = "Migration can be performed only by admin")]
fn transfer_with_migration_message_by_not_admin() {
let alice = accounts(0);
let admin = accounts(1);

let reference_product = Product::generate("product").enabled(true).cap(0, 1_000_000);
let reference_restakable_product = Product::generate("restakable_product").enabled(true).cap(0, 1_000_000);

let mut context = Context::new(admin).with_products(&[reference_product.clone(), reference_restakable_product]);

let amount_alice = 1_000;
let msg = json!({
"type": "migrate",
"data": [
{
"id": "cefi_product_3",
"account_id": alice,
"product_id": reference_product.id,
"principal": amount_alice.to_string(),
"created_at": "0",
},
]
});

context.switch_account_to_ft_contract_account();
context
.contract
.ft_on_transfer(alice, U128(amount_alice), msg.to_string());
}

#[test]
#[should_panic(expected = "Unable to deserialize msg")]
fn transfer_with_unknown_message() {
Expand Down
34 changes: 32 additions & 2 deletions contract/src/migration/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,36 @@ use crate::{
};

impl Contract {
/// Migrates `CeFi Jars` to create `DeFi Jars`.
///
/// This method receives a list of entities called `CeFiJar`, which represent token deposits
/// from a 3rd party service, and creates corresponding `DeFi Jars` for them. In order to support
/// the transition of deposit terms from the 3rd party to the contract, the `Product` with these
/// terms must be registered beforehand.
///
/// # Arguments
///
/// - `jars`: A vector of `CeFiJar` entities representing token deposits from a 3rd party service.
/// - `total_received`: The total amount of tokens received, ensuring that all tokens are distributed
/// correctly.
///
/// # Panics
///
/// This method can panic in following cases:
///
/// 1. If a `Product` required to create a Jar is not registered. In such a case, the migration
/// process cannot proceed, and the method will panic.
///
/// 2. If the total amount of tokens received is not equal to the sum of all `CeFiJar` entities.
/// This panic ensures that all deposits are properly migrated, and any discrepancies will trigger
/// an error.
///
/// 3. Panics in case of unauthorized access by non-admin users.
///
/// # Authorization
///
/// This method can only be called by the Contract Admin. Unauthorized access will result in a panic.
///
pub(crate) fn migrate_jars(&mut self, jars: Vec<CeFiJar>, total_received: U128) {
let mut event_data: Vec<MigrationEventItem> = vec![];
let mut total_amount: TokenAmount = 0;
Expand Down Expand Up @@ -52,8 +82,8 @@ impl Contract {
});
}

assert_eq!(
total_received.0, total_amount,
require!(
total_received.0 == total_amount,
"Total received doesn't match the sum of principals"
);

Expand Down
Binary file added docs/migration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 43 additions & 14 deletions docs/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,24 @@ The DeFi Jars contract allows users to stake their NEP-141 fungible tokens and a

The contract enables a contract administrator to register Products describing the terms of deposits. Users can then stake FTs into Jars, which are deposits based on the specified Products. The contract also provides the ability to restrict the creation of Jars for specific Products based on external conditions and involves third-party services.

## πŸ“– Terminology
## Table of content
1. [**Terminology**](#1--terminology)
2. [**Functional Requirements**](#2--functional-requirements)
1. [Roles](#21--roles)
2. [Features](#22--features)
3. [Use cases](#23--use-cases)
3. [**Technical requirements**](#3--technical-requirements)
1. [Project structure](#31--project-structure)
1. [Tooling](#311--tooling)
2. [Artifacts](#312--artifacts)
3. [Codebase](#313--codebase)
4. [Integration tests](#314--integration-tests)
2. [Architecture overview](#32--architecture-overview)
1. [Actors](#321--actors)
2. [Security](#322--security)
3. [Migration strategy](#33--migration-strategy)

## 1. πŸ“– Terminology

The contract operates with the following entities:

Expand All @@ -27,17 +44,17 @@ The contract allows users to perform the following actions:
- **Restake:** This refers to the act of re-enacting a previous β€œstake” action under the same terms.
- **Claim:** This is the act of a user requesting the smart contract to release the accrued earnings from applied ERs on all or selected Jars containing funds.

## 1. πŸ§‘β€πŸ’Ό Functional Requirements
## 2. πŸ§‘β€πŸ’Ό Functional Requirements

### 1.1. πŸ‘€ Roles
### 2.1. πŸ‘€ Roles

The DeFi Jars contract defines three roles:

- **Admin:** The Admin role manages Products. It can register new Products, enable or disable existing Products, and change verifying (public) keys.
- **User:** Users can create Jars by staking tokens, claim accrued interest, unstake their funds, and restake mature Jars.
- **Oracle:** While not directly represented in the contract, the Oracle role can issue signatures to restrict Users' access to specific Products based on conditions that cannot be evaluated within the contract itself.

### 1.2. βš™οΈ Features
### 2.2. βš™οΈ Features

The DeFi Jars contract provides the following features:

Expand All @@ -50,7 +67,7 @@ The DeFi Jars contract provides the following features:
- Claim accrued $SWEAT from a Jar (User).
- Top up the $SWEAT balance of a Jar (User).

### 1.3. πŸ§‘β€πŸ’» Use cases
### 2.3. πŸ§‘β€πŸ’» Use cases

1. Admin can register a new Fixed Product. It must contain the Product ID, APY, Jar capacity, withdrawal fee, an optional verifying (public) key, lockup term, and indicators regarding whether it's enabled right after registration, allows top-ups, and allows restaking.
2. Admin can register a new Flexible Product. It must contain the Product ID, APY, Jar capacity, withdrawal fee, an optional verifying (public) key, and indicators regarding whether it's enabled right after registration.
Expand All @@ -71,7 +88,7 @@ The DeFi Jars contract provides the following features:
17. User can top up the principal of a Flexible Jar or Fixed Jar if the related Fixed Product allows top-ups.
18. User can restake a Fixed Jar after its maturity. On restake, a new Jar is created, and the principal of the original Jar is transferred to the new one.

## 2. πŸ€– Technical requirements
## 3. πŸ€– Technical requirements

DeFi Jar contract is a smart contract for NEAR network. It has been developed with Rust language using
[near-sdk-rs](https://github.com/near/near-sdk-rs).
Expand All @@ -80,7 +97,7 @@ Integration tests are NEAR Workspaces ([workspaces-rs](https://github.com/near/n

The smart contract uses [ed25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek/tree/main/ed25519-dalek) to verify signatures for Premium Products.

### 2.1. 🧬 Project structure
### 3.1. 🧬 Project structure

Here is an overview of the project structure:

Expand All @@ -99,7 +116,7 @@ Here is an overview of the project structure:

Start by reading `README.md` to access comprehensive information about building, testing, and deploying a smart contract.

#### 2.1.1. πŸ› οΈ Tooling
#### 3.1.1. πŸ› οΈ Tooling

The `Makefile` contains useful commands for building, testing, and deploying the contract.
These commands either operate on `cargo` or run scripts found in the `scripts` directory.
Expand All @@ -109,14 +126,14 @@ To view all the available commands for `make`, use the following command:
make help
```

#### 2.1.2. πŸ“¦ Artifacts
#### 3.1.2. πŸ“¦ Artifacts

The `res` directory contains WASM binaries:

- **sweat.wasm**: Assembled FT token contract for testing purposes.
- **sweat_jar.wasm**: The actual version of the DeFi Jar contract.

#### 2.1.2. πŸ’Ώ Codebase
#### 3.1.3. πŸ’Ώ Codebase

Under the `./contract` directory, you can locate the smart contract module. Project configuration and dependencies are
found in the `Cargo.toml` file. The lib.rs file contains the contract data structure and initialization code.
Expand All @@ -133,7 +150,7 @@ The `ft_interface.rs` file contains helpers to facilitate interaction with the r

The code in `ft_receiver.rs` handles incoming Token transfers. This mechanism is used for Jar creation, top-ups, and migration.

#### 2.1.3. πŸ§ͺ Integration tests
#### 3.1.4. πŸ§ͺ Integration tests

The `./integration-tests` directory contains integration tests for the smart contract.
These tests work with both FT and DeFi Jars contracts, covering the following scenarios:
Expand All @@ -148,9 +165,9 @@ In addition to these files, it also contains utilities and testing data, with th
- **ft_contract_interface.rs:** This offers an interface for the FT Contract API.
- **jar_contract_interface.rs:** This provides an interface for the DeFi Jar Contract API.

## 2.2. πŸ“ Architecture overview
## 3.2. πŸ“ Architecture overview

### 2.2.1. 🎭 Actors
### 3.2.1. 🎭 Actors

The contract involves the participation of the following entities:

Expand All @@ -163,7 +180,7 @@ Refer to the following chart for a detailed overview of the entities involved wi

![staking architecture](staking_architecture.png)

### 2.2.2. πŸ” Security
### 3.2.2. πŸ” Security

To prevent data tampering and unauthorized access to Products, an [Ed25519 signature system](https://ed25519.cr.yp.to/)
is utilized. When authorization checks are necessary to create Jars for a Product, the Product must include
Expand All @@ -178,3 +195,15 @@ This signature must be included along with other required arguments. The Contrac
to the one signed by the Oracle, incorporating the provided arguments and contextual data.
Subsequently, the Contract verifies this message against the Signature, using the Product's public key, to ensure
the prevention of tampering.

## 3.3. πŸšƒ Migration strategy

Sweat Economy already offers a CeFi staking product. One of the goals is to migrate these centralized deposits to the blockchain.
Migration occurs on-demand, and a User must choose to opt into the DeFi Jars to migrate their existing deposits.
To prepare for migration, an Admin must create Products that reflect those existing in the third-party CeFi service.
These Products can be disabled from the beginning, and it shouldn't impact the migration process. This feature is convenient for managing legacy Products.
The Migration API provides a batched method for migration, thus enabling flexibility for Oracle implementation.

Here's a chart illustrating the migration process:

![migration](migration.png)
Binary file modified res/sweat_jar.wasm
Binary file not shown.

0 comments on commit c3f13e6

Please sign in to comment.