From f918b39476eca89db71aa9abb547914db55ec8b1 Mon Sep 17 00:00:00 2001 From: Peterjah Date: Wed, 11 Dec 2024 15:43:32 +0100 Subject: [PATCH] prepare migration --- .../assembly/contracts/legacy_mns.ts | 611 ++++++++++++++++++ smart-contract/assembly/contracts/main.ts | 59 +- smart-contract/package.json | 2 + smart-contract/src/deploy.ts | 20 +- smart-contract/src/infos.ts | 30 + smart-contract/src/migrate.ts | 66 ++ smart-contract/src/utils.ts | 27 + 7 files changed, 811 insertions(+), 4 deletions(-) create mode 100644 smart-contract/assembly/contracts/legacy_mns.ts create mode 100644 smart-contract/src/infos.ts create mode 100644 smart-contract/src/migrate.ts diff --git a/smart-contract/assembly/contracts/legacy_mns.ts b/smart-contract/assembly/contracts/legacy_mns.ts new file mode 100644 index 0000000..441a3d4 --- /dev/null +++ b/smart-contract/assembly/contracts/legacy_mns.ts @@ -0,0 +1,611 @@ +// The entry file of your WebAssembly module. +import { + Address, + Context, + Storage, + balance, + call, + functionExists, + generateEvent, + getKeys, + getKeysOf, + isDeployingContract, + setBytecode, + transferCoins, +} from '@massalabs/massa-as-sdk'; +import { + Args, + boolToByte, + bytesToString, + bytesToU256, + stringToBytes, + u256ToBytes, + u64ToBytes, +} from '@massalabs/as-types'; +import { + _approve, + _balanceOf, + _constructor, + _getApproved, + _isApprovedForAll, + _name, + _ownerOf, + _setApprovalForAll, + _symbol, + _update, + _transferFrom, +} from '@massalabs/sc-standards/assembly/contracts/MRC721/MRC721-internals'; +import { + _setOwner, + _onlyOwner, + _isOwner, +} from '@massalabs/sc-standards/assembly/contracts/utils/ownership-internal'; +export { + setOwner, + ownerAddress, +} from '@massalabs/sc-standards/assembly/contracts/utils/ownership'; +import { u256 } from 'as-bignum/assembly'; + +const MNS_BUILDNET = 'AS12qKAVjU1nr66JSkQ6N4Lqu4iwuVc6rAbRTrxFoynPrPdP1sj3G'; +/** + * This function is meant to be called only one time: when the contract is deployed. + * + * @param _binaryArgs - Arguments serialized with Args (none) + */ +export function constructor(_binaryArgs: StaticArray): void { + _constructor('MassaNameService', 'MNS'); + _setOwner(Context.caller().toString()); + + // Clone MNS contract storage + const keys = getKeysOf(MNS_BUILDNET); + let count = 0; + const MNS = new Address(MNS_BUILDNET); + for (count = 0; count < keys.length; count++) { + Storage.set(keys[count], Storage.getOf(MNS, keys[count])); + } + + const counter = Storage.get(COUNTER_KEY); + Storage.set(COUNTER_KEY, counter); + generateEvent( + `Cloned ${bytesToU256(counter).toString()} tokens from MNS contracts`, + ); + + return; +} + +export function mintBatch(_binaryArgs: StaticArray): void { + const NB_DOMAINS = 4000; + const counter = bytesToU256(Storage.get(COUNTER_KEY)); + const dummyOwner = 'AU12TLws65LJcyQLfzVUsu8yhWYLE4Mb99aksSBoKsShr1H5tiHQT'; + const dummyTarget = 'AS1qyye6EfPSvj8whjEeUCZepD3mnhYvaaLub65DGcF6jdxydBPs'; + + const counterI32 = counter.toI32(); + for (let i = 0; i < NB_DOMAINS; i++) { + const index = counterI32 + i; + const domain = 'm' + index.toString(); + const domainBytes = stringToBytes(domain); + // Transfer ownership of the domain to the caller + _update(dummyOwner, u256.fromI32(index), ''); + + Storage.set(buildDomainKey(counter), domainBytes); + Storage.set(buildTokenIdKey(domain), u256ToBytes(counter)); + + const addressKey = buildAddressKey(dummyTarget); + + Storage.set(addressKey, domainBytes); + } + // @ts-ignore + Storage.set(COUNTER_KEY, u256ToBytes(u256.fromI32(NB_DOMAINS) + counter)); + + generateEvent( + `Minted ${NB_DOMAINS} tokens. CURRENT COUNTER: ${ + NB_DOMAINS + counter.toI32() + }`, + ); +} + +// DNS RELATED FUNCTIONS + +const DOMAIN_SEPARATOR_KEY: StaticArray = [0x42]; + +const COUNTER_KEY: StaticArray = [0x00]; +const TOKEN_ID_KEY_PREFIX: StaticArray = [0x01]; +const TARGET_KEY_PREFIX: StaticArray = [0x02]; +const DOMAIN_KEY_PREFIX: StaticArray = [0x03]; +const ADDRESS_KEY_PREFIX: StaticArray = [0x04]; +const LOCKED_KEY_PREFIX: StaticArray = [0x05]; + +// Be careful if we edit the values here to increase the price, it requires to change the refund +// logic in dnsFree function to avoid refunding more than the user paid with the old prices. +function calculateCreationCost(sizeDomain: u64): u64 { + if (sizeDomain <= 2) { + return 10_000_000_000_000; + } else if (sizeDomain == 3) { + return 1_000_000_000_000; + } else if (sizeDomain == 4) { + return 100_000_000_000; + } else if (sizeDomain == 5) { + return 10_000_000_000; + } + return 1_000_000_000; +} + +// @ts-ignore (fix for IDE) +@inline +function isNotNumber(c: i32): bool { + const zero = 48; + const nine = 57; + return c < zero || c > nine; +} + +// @ts-ignore (fix for IDE) +@inline +function isNotLowercaseLetter(c: i32): bool { + const a = 97; + const z = 122; + return c < a || c > z; +} + +// @ts-ignore (fix for IDE) +@inline +function isNotHyphen(c: i32): bool { + return c != 45; +} + +export function isValidDomain(domain: string): bool { + if (domain.length < 2 || domain.length > 100) { + return false; + } + for (let i = 0; i < domain.length; i++) { + const c = domain.charCodeAt(i); + // Must be lowercase or hyphen + if (isNotNumber(c) && isNotLowercaseLetter(c) && isNotHyphen(c)) { + return false; + } + } + return true; +} + +function buildTokenIdKey(domain: string): StaticArray { + return DOMAIN_SEPARATOR_KEY.concat( + TOKEN_ID_KEY_PREFIX.concat(stringToBytes(domain)), + ); +} + +function buildDomainKey(tokenId: u256): StaticArray { + return DOMAIN_SEPARATOR_KEY.concat( + DOMAIN_KEY_PREFIX.concat(u256ToBytes(tokenId)), + ); +} + +function buildTargetKey(domain: string): StaticArray { + return DOMAIN_SEPARATOR_KEY.concat( + TARGET_KEY_PREFIX.concat(stringToBytes(domain)), + ); +} + +function buildAddressKey(address: string): StaticArray { + return DOMAIN_SEPARATOR_KEY.concat( + ADDRESS_KEY_PREFIX.concat(stringToBytes(address)), + ); +} + +function buildLockedKey(): StaticArray { + return DOMAIN_SEPARATOR_KEY.concat(LOCKED_KEY_PREFIX); +} + +function addressIsEOA(address: Address): bool { + const stringAddress = address.toString(); + return stringAddress.startsWith('AU'); +} + +/** + * Lock the contract + */ +export function dnsLock(_: StaticArray): void { + _onlyOwner(); + Storage.set(buildLockedKey(), u256ToBytes(u256.Zero)); +} + +/** + * Unlock the contract + */ +export function dnsUnlock(_: StaticArray): void { + _onlyOwner(); + Storage.del(buildLockedKey()); +} + +/** + * Calculate the cost of the dns allocation + * @param binaryArgs - (domain: string, target: string) + * + * @returns cost of the dns allocation as u64 + */ +export function dnsAllocCost(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const domain = args + .nextString() + .expect('domain argument is missing or invalid'); + assert(isValidDomain(domain), 'Invalid domain'); + return u64ToBytes(calculateCreationCost(domain.length) + 100_000_000); +} + +/** + * Register domain + * @param binaryArgs - (domain: string, target: string) + * @returns tokenId of the dns as u256 + */ +export function dnsAlloc(binaryArgs: StaticArray): StaticArray { + if (Storage.has(buildLockedKey()) && !_isOwner(Context.caller().toString())) { + throw new Error('Domain allocation is locked'); + } + const initialBalance = balance(); + const args = new Args(binaryArgs); + const domain = args + .nextString() + .expect('domain argument is missing or invalid'); + const target = args + .nextString() + .expect('target argument is missing or invalid'); + const owner = Context.caller().toString(); + + assert(isValidDomain(domain), 'Invalid domain'); + const targetKey = buildTargetKey(domain); + assert(!Storage.has(targetKey), 'Domain already registered'); + Storage.set(targetKey, stringToBytes(target)); + + assert(Storage.has(COUNTER_KEY), 'Counter not initialized'); + const counter = bytesToU256(Storage.get(COUNTER_KEY)); + // Transfer ownership of the domain to the caller + _update(owner, counter, ''); + + Storage.set(buildDomainKey(counter), stringToBytes(domain)); + Storage.set(buildTokenIdKey(domain), u256ToBytes(counter)); + + let entries: string[] = []; + const addressKey = buildAddressKey(target); + if (Storage.has(addressKey)) { + entries = bytesToString(Storage.get(addressKey)).split(','); + } + entries.push(domain); + Storage.set(addressKey, stringToBytes(entries.join(','))); + + // @ts-ignore (fix for IDE) + Storage.set(COUNTER_KEY, u256ToBytes(counter + u256.One)); + const finalBalance = balance(); + const storageCosts = initialBalance - finalBalance; + const totalCost = calculateCreationCost(domain.length) + storageCosts; + const transferredCoins = Context.transferredCoins(); + assert( + transferredCoins >= totalCost, + 'Insufficient funds to register domain. Provided:' + + transferredCoins.toString() + + '. Needed: ' + + totalCost.toString() + + '.', + ); + if (transferredCoins > totalCost) { + const amountToSend = transferredCoins - totalCost; + if (addressIsEOA(Context.caller())) { + transferCoins(Context.caller(), amountToSend); + } else { + if (functionExists(Context.caller(), 'receiveCoins')) { + call(Context.caller(), 'receiveCoins', new Args(), amountToSend); + } + } + } + return u256ToBytes(counter); +} + +/** + * Free domain and refund half of the registration fee + * @param binaryArgs - (tokenId: u256) + * @returns void + */ +export function dnsFree(binaryArgs: StaticArray): void { + if (Storage.has(buildLockedKey()) && !_isOwner(Context.caller().toString())) { + throw new Error('Free is locked'); + } + const initialBalance = balance(); + const args = new Args(binaryArgs); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + + const domainKey = buildDomainKey(tokenId); + assert(Storage.has(domainKey), 'Domain not registered'); + const owner = _ownerOf(tokenId); + assert(new Address(owner) == Context.caller(), 'Only owner can free domain'); + + const domain = bytesToString(Storage.get(domainKey)); + Storage.del(domainKey); + // Transfer ownership of the domain to empty address + _update('', tokenId, ''); + + let targetKey = buildTargetKey(domain); + let target = bytesToString(Storage.get(targetKey)); + let addressDomains = bytesToString( + Storage.get(buildAddressKey(target)), + ).split(','); + const index = addressDomains.indexOf(domain); + addressDomains.splice(index, 1); + if (addressDomains.length == 0) { + Storage.del(buildAddressKey(target)); + } else { + Storage.set( + buildAddressKey(target), + stringToBytes(addressDomains.join(',')), + ); + } + + Storage.del(targetKey); + Storage.del(buildTokenIdKey(domain)); + const finalBalance = balance(); + const storageCostsRefunded = finalBalance - initialBalance; + const refundTotal = + calculateCreationCost(domain.length) / 2 + + storageCostsRefunded + + Context.transferredCoins(); + if (addressIsEOA(Context.caller())) { + transferCoins(Context.caller(), refundTotal); + } else { + if (functionExists(Context.caller(), 'receiveCoins')) { + call(Context.caller(), 'receiveCoins', new Args(), refundTotal); + } + } + return; +} + +/** + * Get the target address associated with a domain + * @param args - (domain: string) + * @returns Address target of the domain + */ +export function dnsResolve(args: StaticArray): StaticArray { + const argsObj = new Args(args); + const domain = argsObj + .nextString() + .expect('domain argument is missing or invalid'); + const target = Storage.get(buildTargetKey(domain)); + return target; +} + +/** Get a list of domain associated with an address + * @param args - (address: string) + * + * @returns List of domains as string separated by comma + */ +export function dnsReverseResolve(args: StaticArray): StaticArray { + const argsObj = new Args(args); + const address = argsObj + .nextString() + .expect('address argument is missing or invalid'); + return Storage.get(buildAddressKey(address)); +} + +/** + * Update the target address associated with a domain. Only the owner can update the target. + * @param binaryArgs - (domain: string, newTarget: string) + */ +export function dnsUpdateTarget(binaryArgs: StaticArray): void { + if (Storage.has(buildLockedKey()) && !_isOwner(Context.caller().toString())) { + throw new Error('Update Target is locked'); + } + const argsObj = new Args(binaryArgs); + const domain = argsObj + .nextString() + .expect('domain argument is missing or invalid'); + const newTarget = argsObj + .nextString() + .expect('target argument is missing or invalid'); + + const tokenId = bytesToU256(Storage.get(buildTokenIdKey(domain))); + const owner = _ownerOf(tokenId); + assert( + new Address(owner) == Context.caller(), + 'Only owner can update target', + ); + + const previousTarget = bytesToString(Storage.get(buildTargetKey(domain))); + const addressDomains = bytesToString( + Storage.get(buildAddressKey(previousTarget)), + ).split(','); + const index = addressDomains.indexOf(domain); + addressDomains.splice(index, 1); + if (addressDomains.length == 0) { + Storage.del(buildAddressKey(previousTarget)); + } else { + Storage.set( + buildAddressKey(previousTarget), + stringToBytes(addressDomains.join(',')), + ); + } + + let entries: string[] = []; + const addressKey = buildAddressKey(newTarget); + if (Storage.has(addressKey)) { + entries = bytesToString(Storage.get(addressKey)).split(','); + } + entries.push(domain); + Storage.set(addressKey, stringToBytes(entries.join(','))); + + Storage.set(buildTargetKey(domain), stringToBytes(newTarget)); +} + +/** + * Upgrade the DNS smart contract bytecode + * @param args - new bytecode + * @returns void + */ +export function upgradeSC(args: StaticArray): void { + _onlyOwner(); + setBytecode(args); +} + +/** + * Transfer internal coins to another address + * @param binaryArgs - (to: string, amount: u64) + * @returns void + */ +export function transferInternalCoins(binaryArgs: StaticArray): void { + _onlyOwner(); + const argsObj = new Args(binaryArgs); + const to = argsObj.nextString().expect('to argument is missing or invalid'); + const amount = argsObj + .nextU64() + .expect('amount argument is missing or invalid'); + transferCoins(new Address(to), amount); +} + +/** + * Get the tokenId of the domain + * @param binaryArgs - (domain: string) + * @returns tokenId of the domain as u256 + */ +export function getTokenIdFromDomain( + binaryArgs: StaticArray, +): StaticArray { + const args = new Args(binaryArgs); + const domain = args + .nextString() + .expect('domain argument is missing or invalid'); + if (!Storage.has(buildTokenIdKey(domain))) { + throw new Error('Domain not found'); + } + return Storage.get(buildTokenIdKey(domain)); +} + +/** + * Get the domain from the tokenId + * @param binaryArgs - (tokenId: u256) + * @returns domain of the tokenId + */ +export function getDomainFromTokenId( + binaryArgs: StaticArray, +): StaticArray { + const args = new Args(binaryArgs); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + return Storage.get(buildDomainKey(tokenId)); +} + +// NFT RELATED FUNCTIONS + +/** + * Get the name of the NFT collection + * @returns name of the NFT collection + */ +export function name(): StaticArray { + return stringToBytes(_name()); +} + +/** + * Get the symbol of the NFT collection + * @returns symbol of the NFT collection + */ +export function symbol(): StaticArray { + return stringToBytes(_symbol()); +} + +/** + * Returns the number of tokens owned by the address + * @param binaryArgs - (address: string) + * @returns Number of tokens owned by the address in u256 as bytes + */ +export function balanceOf(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const address = args + .nextString() + .expect('address argument is missing or invalid'); + return u256ToBytes(_balanceOf(address)); +} + +/** + * Get the owner of the token + * @param binaryArgs - (tokenId: u256) + * @returns Address of the owner of the token + */ +export function ownerOf(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + const owner = _ownerOf(tokenId); + if (owner == '') { + throw new Error('Token id not found'); + } + return stringToBytes(owner); +} + +/** + * Transfer token from one address to another + * @param binaryArgs - (from: string, to: string, tokenId: u256) + */ +export function transferFrom(binaryArgs: StaticArray): void { + if (Storage.has(buildLockedKey()) && !_isOwner(Context.caller().toString())) { + throw new Error('Transfer is locked'); + } + const args = new Args(binaryArgs); + const from = args.nextString().expect('from argument is missing or invalid'); + const to = args.nextString().expect('to argument is missing or invalid'); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + _transferFrom(from, to, tokenId); +} + +/** + * Approve the address to transfer the token + * @param binaryArgs - (to: string, tokenId: u256) + */ +export function approve(binaryArgs: StaticArray): void { + const args = new Args(binaryArgs); + const to = args.nextString().expect('to argument is missing or invalid'); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + _approve(to, tokenId); +} + +/** + * Set approval for all tokens of the owner + * @param binaryArgs - (to: string, approved: bool) + */ +export function setApprovalForAll(binaryArgs: StaticArray): void { + const args = new Args(binaryArgs); + const to = args.nextString().expect('to argument is missing or invalid'); + const approved = args + .nextBool() + .expect('approved argument is missing or invalid'); + _setApprovalForAll(to, approved); +} + +/** + * Get the address approved to transfer the token or empty address if none + * @param binaryArgs - (tokenId: u256) + * @returns Address of the approved address + */ +export function getApproved(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + return stringToBytes(_getApproved(tokenId)); +} + +/** + * Returns if the operator is approved to transfer the tokens of the owner + * @param binaryArgs - (owner: string, operator: string) + * @returns Bool as bytes + */ +export function isApprovedForAll(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const owner = args + .nextString() + .expect('owner argument is missing or invalid'); + const operator = args + .nextString() + .expect('operator argument is missing or invalid'); + return boolToByte(_isApprovedForAll(owner, operator)); +} diff --git a/smart-contract/assembly/contracts/main.ts b/smart-contract/assembly/contracts/main.ts index d10e19e..6173005 100644 --- a/smart-contract/assembly/contracts/main.ts +++ b/smart-contract/assembly/contracts/main.ts @@ -6,6 +6,7 @@ import { balance, call, functionExists, + generateEvent, isDeployingContract, setBytecode, transferCoins, @@ -22,10 +23,12 @@ import { _update, _constructor, _ownerOf, + _increaseTotalSupply, + _getOwnedTokensKeyPrefix, + TOTAL_SUPPLY_KEY, } from '@massalabs/sc-standards/assembly/contracts/MRC721/enumerable/MRC721Enumerable-internals'; // eslint-disable-next-line max-len import { transferFrom as _transferFrom } from '@massalabs/sc-standards/assembly/contracts/MRC721/enumerable/MRC721Enumerable'; - import { _setOwner, _onlyOwner, @@ -458,6 +461,60 @@ export function transferFrom(binaryArgs: StaticArray): void { _transferFrom(binaryArgs); } +// TEMP CODE +const MIGRATE_COUNTER_KEY: StaticArray = [0x06]; + +function migrateCounterdKey(): StaticArray { + return DOMAIN_SEPARATOR_KEY.concat(MIGRATE_COUNTER_KEY); +} + +export function migrate(_binaryArgs: StaticArray): void { + const BATCH_SIZE = 3000; + + let migrateCounter = u256.Zero; + if (Storage.has(migrateCounterdKey())) { + migrateCounter = bytesToU256(Storage.get(migrateCounterdKey())); + } + if (!Storage.has(TOTAL_SUPPLY_KEY)) { + Storage.set(TOTAL_SUPPLY_KEY, u256ToBytes(u256.Zero)); + } + const legacyCounter = bytesToU256(Storage.get(COUNTER_KEY)).toI32(); + + const migrateCountStart = migrateCounter.toI32(); + let i = 0; + for (i = 0; i < BATCH_SIZE; i++) { + const tokenIdI32 = i + migrateCountStart; + const tokenId = u256.fromI32(tokenIdI32); + + if (tokenIdI32 >= legacyCounter) { + generateEvent(`Reached total counter ${legacyCounter}`); + break; + } + + const owner = _ownerOf(tokenId); + if (owner == '') { + generateEvent(`Token ${tokenIdI32} has no owner. Skipping.`); + continue; + } + + // not exported + // _addTokenToOwnerEnumeration(to, tokenId); + const key = _getOwnedTokensKeyPrefix(owner).concat(u256ToBytes(tokenId)); + Storage.set(key, []); + } + + _increaseTotalSupply(u256.fromI32(i)); + + // update migration counter + Storage.set( + migrateCounterdKey(), + u256ToBytes(u256.fromI32(i + migrateCountStart)), + ); + generateEvent( + `Migrated ${i} tokens. Total migrated: ${i + migrateCountStart}`, + ); +} + export { setOwner, ownerAddress, diff --git a/smart-contract/package.json b/smart-contract/package.json index 6d33e0c..465e1ba 100644 --- a/smart-contract/package.json +++ b/smart-contract/package.json @@ -9,6 +9,8 @@ "clean": "rimraf build", "deploy": "npm run build && tsx src/deploy.ts", "testDns": "tsx src/test.ts", + "info": "tsx src/infos.ts", + "migrate": "tsx src/migrate.ts", "prettier": "prettier '**/src/**/*.ts' --check && as-prettier --check assembly", "prettier:fix": "prettier '**/src/**/*.ts' --write && as-prettier --write assembly", "lint": "eslint .", diff --git a/smart-contract/src/deploy.ts b/smart-contract/src/deploy.ts index 0b9a3d7..6a6ab55 100644 --- a/smart-contract/src/deploy.ts +++ b/smart-contract/src/deploy.ts @@ -9,12 +9,15 @@ import { getScByteCode } from './utils'; import { config } from 'dotenv'; config(); +let op; +let events; + const account = await Account.fromEnv(); const provider = Web3Provider.buildnet(account); console.log('Deploying contract...'); -const byteCode = getScByteCode('build', 'main.wasm'); +const byteCode = getScByteCode('build', 'legacy_mns.wasm'); const name = 'Massa'; const constructorArgs = new Args().addString(name); @@ -23,15 +26,26 @@ const contract = await SmartContract.deploy( provider, byteCode, constructorArgs, - { coins: Mas.fromString('0.1'), waitFinalExecution: false }, + { coins: Mas.fromString('10'), waitFinalExecution: false }, ); console.log('Contract deployed at:', contract.address); -const events = await provider.getEvents({ +events = await provider.getEvents({ smartContractAddress: contract.address, }); for (const event of events) { console.log('Event message:', event.data); } + +op = await contract.call('mintBatch', undefined, { + coins: Mas.fromString('70'), +}); +events = await op.getSpeculativeEvents(); + +for (const event of events) { + console.log('mintBatch Events:', event.data); +} + +console.log('mintBatch done ! operation:', op.id); diff --git a/smart-contract/src/infos.ts b/smart-contract/src/infos.ts new file mode 100644 index 0000000..e1a87c9 --- /dev/null +++ b/smart-contract/src/infos.ts @@ -0,0 +1,30 @@ +import 'dotenv/config'; +import { + Account, + Web3Provider, + MNS_CONTRACTS, + U256, +} from '@massalabs/massa-web3'; +import { getTokenCounter } from './utils'; + +const MNS_CONTRACT = 'AS12v3VzogyYzMj7uL1oyfmJ5HeGgqJ9L5WgguLYg3v6aTfUEqrK3'; +// const MNS_CONTRACT = MNS_CONTRACTS.buildnet; +const IS_MAINNET = false; + +const account = await Account.fromEnv(); +const provider = IS_MAINNET + ? Web3Provider.mainnet(account) + : Web3Provider.buildnet(account); + +const DOMAIN_SEPARATOR_KEY = [0x42]; +const TOKEN_ID_KEY_PREFIX = [0x1]; + +const count = await getTokenCounter(provider, MNS_CONTRACT); +console.log('COUNTER KEY:', count.toString()); + +const tokenIdsFilter = Uint8Array.from([ + ...DOMAIN_SEPARATOR_KEY, + ...TOKEN_ID_KEY_PREFIX, +]); +const balanceKeys = await provider.getStorageKeys(MNS_CONTRACT, tokenIdsFilter); +console.log('Total domains:', balanceKeys.length); diff --git a/smart-contract/src/migrate.ts b/smart-contract/src/migrate.ts new file mode 100644 index 0000000..e80d484 --- /dev/null +++ b/smart-contract/src/migrate.ts @@ -0,0 +1,66 @@ +import { + Account, + Web3Provider, + MNS_CONTRACTS, + SmartContract, + Mas, + OperationStatus, + Operation, + rpcTypes, +} from '@massalabs/massa-web3'; +import 'dotenv/config'; +import { getMigrateCounter, getScByteCode, getTokenCounter } from './utils'; + +const MNS_CONTRACT = 'AS12EufV45Er3LkgQdka5gVrxiEAW1AvD8uXZ2xEZ84VEPVLAgoXY'; +// const MNS_CONTRACT = MNS_CONTRACTS.mainnet; +const IS_MAINNET = false; + +const account = await Account.fromEnv(); +const provider = IS_MAINNET + ? Web3Provider.mainnet(account) + : Web3Provider.buildnet(account); + +const byteCode = getScByteCode('build', 'main.wasm'); +const contract = new SmartContract(provider, MNS_CONTRACT); + +// todo: pause contract + +let op: Operation; +let events: rpcTypes.OutputEvents; +// op = await contract.call('upgradeSC', byteCode, { +// coins: Mas.fromString('3'), +// }); +// events = await op.getFinalEvents(); + +// for (const event of events) { +// console.log('upgradeSC Events:', event.data); +// } + +// console.log('upgradeSC done ! operation:', op.id); + +const counter = await getTokenCounter(provider, MNS_CONTRACT); +let migrateCount = 0n; +try { + migrateCount = await getMigrateCounter(provider, MNS_CONTRACT); +} catch (e) { + console.log('no migrate counter found'); +} + +while (migrateCount < counter) { + console.log('migrating batch from tokenID', migrateCount.toString()); + op = await contract.call('migrate', undefined, { + coins: Mas.fromString('20'), + }); + + events = await op.getFinalEvents(); + + for (const event of events) { + console.log('migrate Events:', event.data); + } + migrateCount = await getMigrateCounter(provider, MNS_CONTRACT); + console.log('new migrate count:', migrateCount.toString()); +} + +// unpause contract + +console.log('Upgrade done ! operation:'); diff --git a/smart-contract/src/utils.ts b/smart-contract/src/utils.ts index 193f16c..8a0ef5a 100644 --- a/smart-contract/src/utils.ts +++ b/smart-contract/src/utils.ts @@ -1,6 +1,7 @@ import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import path from 'path'; +import { Provider, U256 } from '@massalabs/massa-web3'; export function getScByteCode(folderName: string, fileName: string): Buffer { // Obtain the current file name and directory paths @@ -8,3 +9,29 @@ export function getScByteCode(folderName: string, fileName: string): Buffer { const __dirname = path.dirname(path.dirname(__filename)); return readFileSync(path.join(__dirname, folderName, fileName)); } + +const DOMAIN_SEPARATOR_KEY = [0x42]; + +export async function getTokenCounter( + provider: Provider, + contract: string, +): Promise { + const COUNTER_KEY = Uint8Array.from([0x0]); + const counterData = await provider.readStorage(contract, [COUNTER_KEY]); + return U256.fromBytes(counterData[0]); +} + +export async function getMigrateCounter( + provider: Provider, + contract: string, +): Promise { + const MIGRATE_COUNTER_KEY_IDX = [0x06]; + const MIGRATE_COUNTER_KEY = Uint8Array.from([ + ...DOMAIN_SEPARATOR_KEY, + ...MIGRATE_COUNTER_KEY_IDX, + ]); + const counterData = await provider.readStorage(contract, [ + MIGRATE_COUNTER_KEY, + ]); + return U256.fromBytes(counterData[0]); +}