# Mythos Parachain Migration
* [Pallet Migration](https://github.com/paritytech/project-mythical/tree/feat/migration-pallet/pallets/migration/src)
* [Migration Tooling](https://github.com/blockdeep/mythical-migration-tooling)
## Migration items
### **Balances**
**Requirements**
Balances are fairly simple to migrate. Since the total supply will be already set in the Mythos chain we are not allowed to mint any new tokens, meaning that all the tokens needed for migration of balances from Besu chain must be transferred from an already existing account/s.
Initial strategy **(TBD)** requires the Parachain to have a `Pot account` that holds all the funds that will be used for transfers. This account must not have a private key, to prevent someone from draining it before the migration process, and from an internal extrinsic inside the migration pallet this account will be called by the privileged account (migrator) in order to transfer funds to different accounts.
During the migration process **uphold should stop looking at ethereum**
**Necessary data:**
- address: Besu account address that holds nMyth tokens.
- amount: Amount of tokens that the user currently holds.
**Parachain Requirements:**
Develop extrinsic to set POT account and extrinsic to send funds to the different users (Only callable by migrator).
**Open Questions**
- [ ] What if the user funds to be migrated are lower that the ED?
- [ ] Who is funding the POT account to perform the migration?
---
### **Collections**
On the Besu chain there's around 1300 nft collections (as displayed in the Mythical Block Explorer). This collections are ERC721 smart contracts.
Using pallet-nfts this collections can be created using `create()`. But in order to create the collections there's a few issues that need to be tackled first.
* CollectionId: In ethereum collections are identified by their smart contract address. Of course this is not possible inside pallet-nfts since the collectionId is just a decimal incrementable number.
There needs to be a direct mapping between the identifier in the old chain and the new chain so the proposed solution is to create the collections with a specific Id, this specific Id is the decimal representation of the hex address of the Erc721 smart contract.
`Hex: 0x57793a9e2d73ce447caf5ce98c449bbc5a59b57f`
`Dec: 499385693568310817162526335353003011108559762815`
* Erc721 extra fields: The mythical custom Erc721 has the extra fields "name", "symbol", "initialBaseURI". In order to match the collection config on Besu the parachain must store this data somewhere.
For this pallet-nfts has collectionMetada where a set of bytes can be included alongside the collection. A proposed solution is to store a Json (as bytes) with this fields.
```json
name: "collectionName",
symbol: "CN",
baseURI: "somewhere.com/"
```
* Collection Admin: When a collection is created using create() the owner of the collection is set to the account that called the extrinsic. This can be problematic because the owner will be set to the migrator account instead of the correct owner of the Collection (which should be the same as the owner of the Smart Contract).
At the same time in order to set the collection metadata and issue the tokens the migrator must have admin and issuer roles.
In order to address this the approach will be the following:
* Set the ForceOrigin to the migrator.
* Use force_create() to create the collection with the Smart Contract owner as the collection owner.
* Use set_team() to make the migrator be the Issuer and Admin of the collection
* After migration is done a last step will be needed for cleanup where set_team is called again to restore the priviledges to the Smart contract owner
**Necessary data:**
* address: Erc721 address
* name: name field inside smart contract
* symbol: symbol field iside smart contract
* baseUri: BaseUri used for collection.
* owner: owner of smart contract
**Parachain Requirements:**
In order to set the correct collectionId we need to get around NextCollectionId. Inside the migration pallet an extrinsic to set this storage value is needed (`set_collection_id()`) and values for collections must be of type U256 to be able to turn hex addresses into valid decimal ids.
In order to create the collections a batch call will be used to ensure that the order of calls is correct `batchAll([set_collection_id(id), create(), setCollectionMetadata()])` the upside to this is that if one of this calls fails everything will be reverted meaning that a collection will only be created if all the 3 steps are executed correctly.
**Open Questions**
- [ ] Are there better solutions instead of metadata to store the extra fields? Maybe Attributes
- [ ] How to use the baseURI as a parameter to mint the nfts?
- [ ] Is name and symbol really needed?
---
### **Nfts**
Items inside collections (NFTs) are the most complex part of the migration for two reasons, the sheer amount of them (over 3M) and the setup they require.
**Necessary data:**
* contractAddress: collection identifier as the Nft contrct,
* tokenId: item id,
* tokenUrl: goes in hand with the Uri of the collection,
* owner: current token owner,
* approvals: approved account to manage this token in behalf of the owner (TBD)
**Issue with the owner related to the marketplace**
In order to properly set ownership for NFTs some modifications to the data will be necessary prior to the insertion in the parachain.
Currently on the Marketplace Smart Contract whenever a user creates an Ask the Nft gets transferred to the Marketplace address, to allow the S.C. to operate this token and so that the user can't transfer an offered token.
This is not a functionality that was kept for the pallet, instead when an Ask is created the Nft `Transferable` property is disabled and the item can no longer be transfered until the Ask ends.
This means that all tokens that the marketplace owns correspond to a user and this users should be given ownership over the token on the Parachain.
Possible solutions:
* Mythical modifies their database to set the correct owner.
* On the migration instead of only getting the Nft try getting a join with the Ask table from the DB if there is a match then the data from the marketplace can be used to select the correct item owner.
**Open Questions**
- [ ] What if a value is not correctly set? To reduce audit scope a extrinsic to only purge the item data can be used.
### **Asks**
An ask in the marketplace consists of:
```rust
pub struct Ask<AccountId, Amount, Expiration> {
pub seller: AccountId,
pub price: Amount,
pub expiration: Expiration,
pub fee: Amount,
}
```
Inside the marketplace an Ask is created using the createOrder() extrinsic. This extrinsic has a few extra checks and requirements (like fee_signer signature) to create the asks that would make it very expensive and error prone to create the Asks going this route so the simplest way to migrate them will be through a direct storage insert using an extrinsic.
**Necessary data:**
* collectionId: id of a collection in pallet_nfts
* itemId: id of Nft inside a collection
* seller: Item seller (Must be the owner of the item)
* price: price of the item
* expiration: unix timestamp for expiration of the item (must be higher than current timestamp)
* fee: amount to be charged as a fee
**Parachain Requirements:**
The pallet needs to be tightly coupled to the marketplace pallet and a extrinsic called create_ask() is needed. This extrinsic will directly store an ask identified by (collectionId, itemId) and disable the ability to Transfer the item that is on offer.
**Open Questions**
- [ ] With the rework of the fee system to use set values instead of percentages, what fee value is going to be used for asks in parachain?
- [ ] Should we calculate that amount or should mythical already provide it as a field in the DB?
- [ ] Should expired asks be migrated?
## Migration Required Assumptions
1. No new collections will be created during migration process
2. FULL needed state for migration lives on Mythical's provided DB (There is no missing/wrong data on the DB).
3. Funds needed for the POT account are provided by Mythical (**TBD**)
## Stages
Based on the fact that there's over 3M nfts to be migrated, the process of minting all of them in the new chain will inevitably take a few hours. This can be problematic for Mythical's business.
In order to reduce this downtime to be as low as possible we have decided to split the migration in 2 parts.
Stage 1 will take care of the creation of the largest amount of the data on the parachain (collections and nfts). Migration will start a block **X**, While this is ongoing the Besu chain will continue working and Mythical backend should keep pointing to their original chain.
The goal of stage 1 is to create all collections and nfts alongside their metadata. This will take several hours. After the migration process of this 2 items is complete stage 2 will be initiated.
Stage 2 will comprise of 3 steps, update nfts, set balances and create marketplace orders, and as soon as this stage starts Besu chain must be stopped (at a block **Y**) and the downtime will start.
Since Besu will be running while Stage 1 is ongoing, it is very likely that nft transfers occur so as a first step in Stage 2 the Nfts have to be updated with the changes that took place between block **X** and block **Y**.
Then Balances will be set for all accounts and lastly marketplace Asks will be created.


This process will require 2 different Databases (one for each stage) which adds some complexity to the migration process.
If downtime wasn't a big concern we could only use a single database an a single migration state using a pipeline design (collections -> nfts -> balances -> asks)
## DB Schema
Mythical will be providing a DB with the necessary data, this will be set up in a postgresDB with the following schema.
```sql
table contract (
-- primary key
address varchar(64) not null primary key,
erc_type varchar(64) not null,
name varchar(64) not null default '',
symbol varchar(64) not null default '',
base_uri varchar(255) not null default '',
last_processed_block bigint not null default 0,
created_at_block bigint not null default 0
);
table nft_token
(
-- primary key
contract_address varchar(64) not null,
token_id varchar(64) not null,
previous_owner varchar(64) not null,
owner varchar(64) not null,
last_operation_block bigint not null default 0,
PRIMARY KEY (contract_address, token_id)
);
table wallet_balance (
address varchar(64) not null primary key,
balance_in_wei varchar(32) not null,
block_number bigint not null
);
table active_order (
-- primary key
nft_address varchar(64) not null,
token_id varchar(64) not null,
order_type order_type not null,
price_in_wei bigint not null,
created_at_block bigint not null,
creator varchar(64) not null,
expires_at timestamp null,
PRIMARY KEY(nft_address, token_id, order_type, price_in_wei)
);
```