Skip to content

Commit

Permalink
190 call updatewebsite index after deploying a deweb sc (#191)
Browse files Browse the repository at this point in the history
* Add a simple DeWeb index lib

* Set a fix massa-web3 version to avoid bugs due to new versions

* Remove useless console logs

* Call DeWeb index updateWebsite function after a deploySC
  • Loading branch information
thomas-senechal authored Dec 17, 2024
1 parent 998c184 commit 4696217
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 4 deletions.
2 changes: 1 addition & 1 deletion cli/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"dependencies": {
"@commander-js/extra-typings": "^12.1.0",
"@listr2/prompt-adapter-enquirer": "^2.0.11",
"@massalabs/massa-web3": "^5.0.1-dev",
"@massalabs/massa-web3": "5.0.1-dev.20241212140726",
"commander": "^12.1.0",
"enquirer": "^2.4.1",
"js-sha256": "^0.11.0",
Expand Down
8 changes: 6 additions & 2 deletions cli/src/commands/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const uploadCommand = new Command('upload')
.option('-a, --address <address>', 'Address of the website to edit')
.option('-s, --chunkSize <size>', 'Chunk size in bytes')
.option('-y, --yes', 'Skip confirmation prompt', false)
.option('--noIndex', 'Skip DeWeb index update', false)
.action(async (websiteDirPath, options, command) => {
const globalOptions = command.optsWithGlobals()

Expand All @@ -36,7 +37,8 @@ export const uploadCommand = new Command('upload')
provider,
chunkSize,
websiteDirPath,
options.yes
options.yes,
options.noIndex
)

if (options.address) {
Expand Down Expand Up @@ -76,7 +78,8 @@ async function createUploadCtx(
provider: Web3Provider,
chunkSize: number,
websiteDirPath: string,
skipConfirm: boolean
skipConfirm: boolean,
noIndex: boolean
): Promise<UploadCtx> {
return {
provider: provider,
Expand All @@ -90,6 +93,7 @@ async function createUploadCtx(
chunkSize: chunkSize,
websiteDirPath: websiteDirPath,
skipConfirm: skipConfirm,
noIndex: noIndex,
currentTotalEstimation: 0n,
maxConcurrentOps: 4,
minimalFees: await provider.client.getMinimalFee(),
Expand Down
7 changes: 7 additions & 0 deletions cli/src/lib/index/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const BUILDNET_INDEX_ADDRESS =
'AS1TmA4GNpSYBseNNMXpbAp2trUwZxZy3T1sZ9Qd3Qdn9L8wGbMS'

// TODO: Replace with mainnet address when available
export const MAINNET_INDEX_ADDRESS = ''

export const updateWebsiteFunctionName = 'updateWebsite'
72 changes: 72 additions & 0 deletions cli/src/lib/index/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
Args,
Mas,
Operation,
Provider,
SmartContract,
} from '@massalabs/massa-web3'

import { storageCostForEntry } from '../utils/storage'
import { updateWebsiteFunctionName } from './const'
import { addressToOwnerBaseKey, indexByOwnerBaseKey } from './keys'
import { getWebsiteOwner } from './read'
import { getOwnerFromWebsiteSC, getSCAddress } from './utils'

/**
* Get the owner of a website using its 'OWNER' storage key
* @param provider - The provider instance
* @param address - The address of the website
*/
export async function updateWebsite(
provider: Provider,
address: string
): Promise<Operation> {
const args = new Args().addString(address)

const scAddress = getSCAddress((await provider.networkInfos()).chainId)
const sc = new SmartContract(provider, scAddress)

const estimatedCost = await estimateCost(sc, address)

return sc.call(updateWebsiteFunctionName, args, {
coins: estimatedCost,
})
}

/**
* Estimate the cost in coins to update a website.
* @param sc - The smart contract instance
*/
async function estimateCost(
sc: SmartContract,
address: string
): Promise<Mas.Mas> {
return getWebsiteOwner(sc.provider, address)
.then(async (registeredOwner) => {
const scOwner = await getOwnerFromWebsiteSC(sc, address)

return storageCostForEntry(
BigInt(Math.abs(scOwner.length - registeredOwner.length)),
0n
)
})
.catch(async () => {
// The website does not exist in the index, we have to create it
const owner = await getOwnerFromWebsiteSC(sc, address)
const addressToOwnerPrefix = addressToOwnerBaseKey(address)
const indexByOwnerPrefix = indexByOwnerBaseKey(owner)

const addressToOwnerKeyCost = storageCostForEntry(
BigInt(addressToOwnerPrefix.length) + BigInt(owner.length),
0n
)
const indexByOwnerKeyCost = storageCostForEntry(
BigInt(indexByOwnerPrefix.length) + BigInt(address.length),
0n
)

const totalCost = addressToOwnerKeyCost + indexByOwnerKeyCost

return totalCost
})
}
41 changes: 41 additions & 0 deletions cli/src/lib/index/keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { I32, strToBytes } from '@massalabs/massa-web3'

/**
* Returns the base key for the owner's address.
* @param address - The website address
* @returns The base key for the owner's address
*/
export function addressToOwnerBaseKey(address: string): Uint8Array {
const prefix = strToBytes('\x01')
const lengthBytes = I32.toBytes(BigInt(address.length))
const addressBytes = strToBytes(address)

const result = new Uint8Array(
prefix.length + lengthBytes.length + addressBytes.length
)
result.set(prefix, 0)
result.set(lengthBytes, prefix.length)
result.set(addressBytes, prefix.length + lengthBytes.length)

return result
}

/**
* Returns the base key for the owner's list of websites.
* @param owner - The owner's address
* @returns The base key for the owner's list of websites
*/
export function indexByOwnerBaseKey(owner: string): Uint8Array {
const prefix = strToBytes('\x00')
const lengthBytes = I32.toBytes(BigInt(owner.length))
const ownerBytes = strToBytes(owner)

const result = new Uint8Array(
prefix.length + lengthBytes.length + ownerBytes.length
)
result.set(prefix, 0)
result.set(lengthBytes, prefix.length)
result.set(ownerBytes, prefix.length + lengthBytes.length)

return result
}
27 changes: 27 additions & 0 deletions cli/src/lib/index/read.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { bytesToStr, Provider } from '@massalabs/massa-web3'

import { addressToOwnerBaseKey } from './keys'
import { getSCAddress } from './utils'

/**
* Get the owner of a website according to the index smart contract.
* @param sc - The smart contract instance
* @param address - The address of the website
* @returns The owner of the website
*/
export async function getWebsiteOwner(
provider: Provider,
address: string
): Promise<string> {
const scAddress = getSCAddress((await provider.networkInfos()).chainId)
const prefix = addressToOwnerBaseKey(address)

const keys = await provider.getStorageKeys(scAddress, prefix)
if (keys.length === 0) {
return ''
}

const ownerKey = keys[0]
const ownerKeySliced = ownerKey.slice(prefix.length)
return bytesToStr(ownerKeySliced)
}
37 changes: 37 additions & 0 deletions cli/src/lib/index/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { bytesToStr, CHAIN_ID, SmartContract } from '@massalabs/massa-web3'

import { BUILDNET_INDEX_ADDRESS } from './const'

/**
* Get the owner of a website using its 'OWNER' storage key
* @param sc - The smart contract instance
* @param address - The address of the website
* @returns The owner of the website
*/
export async function getOwnerFromWebsiteSC(
sc: SmartContract,
address: string
): Promise<string> {
const ownerAddress = await sc.provider.readStorage(address, ['OWNER'], true)
if (ownerAddress.length === 0) {
throw new Error(`Could not find owner for website ${address}`)
}

return bytesToStr(ownerAddress[0])
}

/**
* Get the index smart contract address for a given chain id
* @param chainId - The chain id of the network to get the index smart contract address for
* @returns The index smart contract address
*/
export function getSCAddress(chainId: bigint): string {
switch (chainId) {
case CHAIN_ID.Mainnet:
throw new Error('Mainnet is not supported yet')
case CHAIN_ID.Buildnet:
return BUILDNET_INDEX_ADDRESS
default:
throw new Error('Unsupported network')
}
}
18 changes: 18 additions & 0 deletions cli/src/tasks/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ListrEnquirerPromptAdapter } from '@listr2/prompt-adapter-enquirer'
import { formatMas } from '@massalabs/massa-web3'
import { ListrTask } from 'listr2'

import { updateWebsite } from '../lib/index'
import { deployCost, deploySC } from '../lib/website/deploySC'

import { UploadCtx } from './tasks'
Expand Down Expand Up @@ -59,6 +60,23 @@ export function deploySCTask(): ListrTask {
persistentOutput: true,
},
},
{
title: 'Update DeWeb Index',
task: async (ctx, subTask) => {
if (ctx.noIndex) {
subTask.skip('Skipping DeWeb Index update')
return
}

subTask.output =
'Updating the DeWeb Index with the new SC address'
await updateWebsite(provider, ctx.sc.address)
},
rendererOptions: {
outputBar: Infinity,
persistentOutput: true,
},
},
],
{
concurrent: false,
Expand Down
1 change: 1 addition & 0 deletions cli/src/tasks/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface UploadCtx {
provider: Provider
sc?: SmartContract

noIndex: boolean
skipConfirm: boolean
websiteDirPath: string
currentTotalEstimation: bigint
Expand Down

0 comments on commit 4696217

Please sign in to comment.