diff --git a/CHANGELOG.md b/CHANGELOG.md index 2022afe..7233ee6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All notable changes to this project will be documented in this file. +## [1.5.1] - 2022-12-23 + +### New + +- Include network giver account details in `network info ` command. +- Giver's signer must be specified explicitly (default network signer will not be used). +- Include EverNode SE Default Giver Keys to signers repository. +- Solidity compiler accepts file name without extension. +- `network giver` parameters `signer` and `type` are mandatory. + +### Fixed + +- Some `contract` commands failed with `Wait for operation failed` message in case + when account does not exist. +- Changing SE GraphQL port did not affect network endpoints related to this SE container. +- Contract commands that expects contract file name as an arg will report more detailed + error message in case when contract file does not exist. + ## [1.5.0] - 2022-12-23 ### New diff --git a/contracts/GiverV2.abi.json b/contracts/GiverV2.abi.json index 425ee07..050bdad 100644 --- a/contracts/GiverV2.abi.json +++ b/contracts/GiverV2.abi.json @@ -1,64 +1,40 @@ -{ - "ABI version": 2, - "header": [ - "time", - "expire" - ], - "functions": [ - { - "name": "sendTransaction", - "inputs": [ - { - "name": "dest", - "type": "address" - }, - { - "name": "value", - "type": "uint128" - }, - { - "name": "bounce", - "type": "bool" - } - ], - "outputs": [] - }, - { - "name": "getMessages", - "inputs": [], - "outputs": [ - { - "components": [ - { - "name": "hash", - "type": "uint256" - }, - { - "name": "expireAt", - "type": "uint64" - } - ], - "name": "messages", - "type": "tuple[]" - } - ] - }, - { - "name": "upgrade", - "inputs": [ - { - "name": "newcode", - "type": "cell" - } - ], - "outputs": [] - }, - { - "name": "constructor", - "inputs": [], - "outputs": [] - } - ], - "data": [], - "events": [] -} +{"ABI version": 2, + "header": ["time", "expire"], + "functions": [ + { + "name": "upgrade", + "inputs": [ + {"name":"newcode","type":"cell"} + ], + "outputs": [ + ] + }, + { + "name": "sendTransaction", + "inputs": [ + {"name":"dest","type":"address"}, + {"name":"value","type":"uint128"}, + {"name":"bounce","type":"bool"} + ], + "outputs": [ + ] + }, + { + "name": "getMessages", + "inputs": [ + ], + "outputs": [ + {"components":[{"name":"hash","type":"uint256"},{"name":"expireAt","type":"uint64"}],"name":"messages","type":"tuple[]"} + ] + }, + { + "name": "constructor", + "inputs": [ + ], + "outputs": [ + ] + } + ], + "events": [ + ] +} \ No newline at end of file diff --git a/contracts/GiverV2.keys.json b/contracts/GiverV2.keys.json new file mode 100644 index 0000000..e3d8285 --- /dev/null +++ b/contracts/GiverV2.keys.json @@ -0,0 +1,4 @@ +{ + "public": "2ada2e65ab8eeab09490e3521415f45b6e42df9c760a639bcf53957550b25a16", + "secret": "172af540e43a524763dd53b26a066d472a97c4de37d5498170564510608250c3" +} \ No newline at end of file diff --git a/contracts/GiverV2.tvc b/contracts/GiverV2.tvc new file mode 100644 index 0000000..a0aaf25 Binary files /dev/null and b/contracts/GiverV2.tvc differ diff --git a/contracts/GiverV3.abi.json b/contracts/GiverV3.abi.json index a8640dd..a75bcf7 100644 --- a/contracts/GiverV3.abi.json +++ b/contracts/GiverV3.abi.json @@ -1,49 +1,49 @@ { - "ABI version": 2, - "version": "2.2", - "header": ["time", "expire"], - "functions": [ - { - "name": "sendTransaction", - "inputs": [ - {"name":"dest","type":"address"}, - {"name":"value","type":"uint128"}, - {"name":"bounce","type":"bool"} - ], - "outputs": [ - ] - }, - { - "name": "getMessages", - "inputs": [ - ], - "outputs": [ - {"components":[{"name":"hash","type":"uint256"},{"name":"expireAt","type":"uint64"}],"name":"messages","type":"tuple[]"} - ] - }, - { - "name": "upgrade", - "inputs": [ - {"name":"newcode","type":"cell"} - ], - "outputs": [ - ] - }, - { - "name": "constructor", - "inputs": [ - ], - "outputs": [ - ] - } - ], - "data": [ - ], - "events": [ - ], - "fields": [ - {"name":"_pubkey","type":"uint256"}, - {"name":"_constructorFlag","type":"bool"}, - {"name":"m_messages","type":"map(uint256,uint32)"} - ] + "ABI version": 2, + "version": "2.2", + "header": ["time", "expire"], + "functions": [ + { + "name": "sendTransaction", + "inputs": [ + {"name":"dest","type":"address"}, + {"name":"value","type":"uint128"}, + {"name":"bounce","type":"bool"} + ], + "outputs": [ + ] + }, + { + "name": "getMessages", + "inputs": [ + ], + "outputs": [ + {"components":[{"name":"hash","type":"uint256"},{"name":"expireAt","type":"uint64"}],"name":"messages","type":"tuple[]"} + ] + }, + { + "name": "upgrade", + "inputs": [ + {"name":"newcode","type":"cell"} + ], + "outputs": [ + ] + }, + { + "name": "constructor", + "inputs": [ + ], + "outputs": [ + ] + } + ], + "data": [ + ], + "events": [ + ], + "fields": [ + {"name":"_pubkey","type":"uint256"}, + {"name":"_constructorFlag","type":"bool"}, + {"name":"m_messages","type":"map(uint256,uint64)"} + ] } diff --git a/contracts/GiverV3.keys.json b/contracts/GiverV3.keys.json new file mode 100644 index 0000000..e3d8285 --- /dev/null +++ b/contracts/GiverV3.keys.json @@ -0,0 +1,4 @@ +{ + "public": "2ada2e65ab8eeab09490e3521415f45b6e42df9c760a639bcf53957550b25a16", + "secret": "172af540e43a524763dd53b26a066d472a97c4de37d5498170564510608250c3" +} \ No newline at end of file diff --git a/contracts/GiverV3.sol b/contracts/GiverV3.sol new file mode 100644 index 0000000..5f08ed4 --- /dev/null +++ b/contracts/GiverV3.sol @@ -0,0 +1,97 @@ +pragma ton-solidity >= 0.59.0; +pragma AbiHeader time; +pragma AbiHeader expire; + +abstract contract Upgradable { + /* + * Set code + */ + + function upgrade(TvmCell newcode) public virtual { + require(msg.pubkey() == tvm.pubkey(), 101); + tvm.accept(); + tvm.commit(); + tvm.setcode(newcode); + tvm.setCurrentCode(newcode); + onCodeUpgrade(); + } + + function onCodeUpgrade() internal virtual; +} + +contract GiverV3 is Upgradable { + + uint8 constant MAX_CLEANUP_MSGS = 30; + mapping(uint256 => uint64) m_messages; + + modifier acceptOnlyOwner { + require(msg.pubkey() == tvm.pubkey(), 101); + tvm.accept(); + _; + } + + /* + * Publics + */ + + /// @notice Allows to accept simple transfers. + receive() external {} + + /// @notice Transfers grams to other contracts. + function sendTransaction(address dest, uint128 value, bool bounce) public { + dest.transfer(value, bounce, 3); + gc(); + } + + /* + * Privates + */ + + /// @notice Function with predefined name called after signature check. Used to + /// implement custom replay protection with parallel access. + function afterSignatureCheck(TvmSlice body, TvmCell message) private inline + returns (TvmSlice) + { + // owner check + require(msg.pubkey() == tvm.pubkey(), 101); + // load and drop message timestamp (uint64) + (, uint64 expireAt) = body.decode(uint64, uint32); + require(expireAt > now, 57); + uint256 msgHash = tvm.hash(message); + require(!m_messages.exists(msgHash), 102); + + tvm.accept(); + m_messages[msgHash] = expireAt; + + return body; + } + + /// @notice Allows to delete expired messages from dict. + function gc() private inline { + uint counter = 0; + for ((uint256 msgHash, uint64 expireAt) : m_messages) { + if (counter >= MAX_CLEANUP_MSGS) { + break; + } + counter++; + if (expireAt <= now) { + delete m_messages[msgHash]; + } + } + } + + /* + * Get methods + */ + struct Message { + uint256 hash; + uint64 expireAt; + } + function getMessages() public view returns (Message[] messages) { + for ((uint256 msgHash, uint64 expireAt) : m_messages) { + messages.push(Message(msgHash, expireAt)); + } + } + + function onCodeUpgrade() internal override {} +} diff --git a/contracts/SafeMultisigWallet.keys.json b/contracts/SafeMultisigWallet.keys.json new file mode 100644 index 0000000..f05c364 --- /dev/null +++ b/contracts/SafeMultisigWallet.keys.json @@ -0,0 +1,4 @@ +{ + "public": "99c84f920c299b5d80e4fcce2d2054b05466ec9df19532a688c10eb6dd8d6b33", + "secret": "73b60dc6a5b1d30a56a81ea85e0e453f6957dbfbeefb57325ca9f7be96d3fe1a" +} \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index 75e5fd0..145f03a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,13 +1,14 @@ module.exports = { - globals: { - 'ts-jest': { - tsconfig: 'tsconfig.json', + globals: { + "ts-jest": { + tsconfig: "tsconfig.json", + }, }, - }, - moduleFileExtensions: ['ts', 'js'], - transform: { - '^.+\\.(ts|tsx)$': 'ts-jest', - }, - testMatch: ['**/test/**/*.+(ts|js)', '**/__tests__/**/*.+(ts|tsx|js)'], - testEnvironment: 'node', + moduleFileExtensions: ["ts", "js"], + transform: { + "^.+\\.(ts|tsx)$": "ts-jest", + }, + modulePathIgnorePatterns: [".*\\.d\\.ts"], + testMatch: ["**/test/**/*.+(ts)", "**/__tests__/**/*.+(ts|tsx)"], + testEnvironment: "node", } diff --git a/package.json b/package.json index 5e38a6e..cdbc0c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "everdev", - "version": "1.5.0", + "version": "1.5.1", "description": "Everscale Dev Environment", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/__tests__/network.ts b/src/__tests__/network.ts index b460f05..9512b80 100644 --- a/src/__tests__/network.ts +++ b/src/__tests__/network.ts @@ -25,19 +25,13 @@ test("Add network giver by address", async () => { name: "se", address: "0:b5e9240fc2d2f1ff8cbb1d1dee7fb7cae155e5f6320e585fcc685698994a19a5", + type: "GiverV2", signer: "alice", }) expect(new NetworkRegistry().get("se").giver?.name).toEqual("GiverV2") }) test("Add network giver by type", async () => { - await runCommand(consoleTerminal, "network giver", { - name: "se", - type: "GiverV1", - signer: "alice", - }) - expect(new NetworkRegistry().get("se").giver?.name).toEqual("GiverV1") - await runCommand(consoleTerminal, "network giver", { name: "se", type: "GiverV2", @@ -45,13 +39,6 @@ test("Add network giver by type", async () => { }) expect(new NetworkRegistry().get("se").giver?.name).toEqual("GiverV2") - await runCommand(consoleTerminal, "network giver", { - name: "se", - type: "GiverV3", - signer: "alice", - }) - expect(new NetworkRegistry().get("se").giver?.name).toEqual("GiverV3") - await runCommand(consoleTerminal, "network giver", { name: "se", type: "SafeMultisigWallet", @@ -71,7 +58,9 @@ test("Add network giver error", async () => { }) expect(true).toBe(false) } catch (error: any) { - expect(error.message).toBe("Unknown contract type NotExist.") + expect(error.message).toBe( + "Can not resolve giver address: unknown giver type NotExist.", + ) } }) diff --git a/src/controllers/contract/accounts.ts b/src/controllers/contract/accounts.ts index b0a060e..549bf2a 100644 --- a/src/controllers/contract/accounts.ts +++ b/src/controllers/contract/accounts.ts @@ -1,5 +1,5 @@ import { Terminal } from "../../core" -import { Account, AccountOptions } from "@eversdk/appkit" +import { Account, AccountOptions, AccountType } from "@eversdk/appkit" import { NetworkRegistry } from "../network/registry" import { TonClient } from "@eversdk/core" import { SignerRegistry } from "../signer/registry" @@ -90,3 +90,30 @@ export async function getAccount( } return account } + +export type ParsedAccount = { + acc_type: AccountType + code_hash?: string + [name: string]: unknown +} + +export async function getParsedAccount( + account: Account, +): Promise { + try { + return await account.getAccount() + } catch (err) { + const acc = await account.client.net.query_collection({ + collection: "accounts", + filter: { id: { eq: await account.getAddress() } }, + result: "acc_type", + limit: 1, + }) + if (acc.result.length === 0) { + return { + acc_type: AccountType.nonExist, + } + } + throw err + } +} diff --git a/src/controllers/contract/index.ts b/src/controllers/contract/index.ts index 371dcd1..a465353 100644 --- a/src/controllers/contract/index.ts +++ b/src/controllers/contract/index.ts @@ -5,7 +5,7 @@ import { } from "../../core/solFileResolvers" import { Account, AccountType } from "@eversdk/appkit" import { TonClient } from "@eversdk/core" -import { getAccount } from "./accounts" +import { getAccount, getParsedAccount } from "./accounts" import { getRunParams, logRunResult, resolveParams } from "./run" import { NetworkGiver } from "../network/giver" import { NetworkRegistry } from "../network/registry" @@ -164,7 +164,7 @@ export const contractInfoCommand: Command = { throw new Error("File argument or address option must be specified") } const account = await getAccount(terminal, args) - const parsed = await account.getAccount() + const parsed = (await getParsedAccount(account)) as any const accType = parsed.acc_type as AccountType if (account.contract.tvc) { const boc = account.client.boc @@ -225,7 +225,7 @@ export const contractDeployCommand: Command = { }, ) { let account = await getAccount(terminal, args) - const info = await account.getAccount() + const info = await getParsedAccount(account) let accountAddress = await account.getAddress() const dataParams = account.contract.abi.data ?? [] @@ -255,7 +255,7 @@ export const contractDeployCommand: Command = { const topUpValue = parseNanoTokens(args.value) // Prepare an informative message in case of insufficient balance for deployment - const howtoTopupMesssage = () => + const howtoTopupMessage = () => giverInfo?.signer ? `You can use \`everdev contract deploy -v \` command to top it up` : `You have to provide enough balance before deploying in two ways: \n` + @@ -280,7 +280,7 @@ export const contractDeployCommand: Command = { } else { if (info.acc_type === AccountType.nonExist) { throw new Error( - `Account ${accountAddress} doesn't exist.\n${howtoTopupMesssage()}`, + `Account ${accountAddress} doesn't exist.\n${howtoTopupMessage()}`, ) } } @@ -320,7 +320,7 @@ export const contractDeployCommand: Command = { `Account ${accountAddress} has low balance to deploy.\n` + (topUpValue ? `You sent amount which is too small` - : howtoTopupMesssage()), + : howtoTopupMessage()), ) : err } @@ -417,7 +417,7 @@ export const contractRunCommand: Command = { }, ) { const account = await getAccount(terminal, args) - const info = await account.getAccount() + const info = await getParsedAccount(account) if (info.acc_type !== AccountType.active) { throw new Error( `Account ${await account.getAddress()} not deployed or frozen`, diff --git a/src/controllers/network/giver.ts b/src/controllers/network/giver.ts index 9521a75..f14eac2 100644 --- a/src/controllers/network/giver.ts +++ b/src/controllers/network/giver.ts @@ -1,19 +1,15 @@ import { Account, AccountGiver } from "@eversdk/appkit" -import { - abiContract, - KeyPair, - Signer, - signerKeys, - TonClient, -} from "@eversdk/core" +import { Signer, TonClient } from "@eversdk/core" import { KnownContract, + knownContractAddress, knownContractByName, knownContractFromAddress, + KnownContractName, KnownContracts, } from "../../core/known-contracts" import { NetworkGiverInfo } from "./registry" -import { SignerRegistry } from "../signer/registry" +import { SE_DEFAULT_GIVER_SIGNER, SignerRegistry } from "../signer/registry" import { formatTokens } from "../../core/utils" async function giverV2Send( @@ -54,13 +50,6 @@ async function multisigSend( }) } -const seGiverKeys: KeyPair = { - public: "2ada2e65ab8eeab09490e3521415f45b6e42df9c760a639bcf53957550b25a16", - secret: "172af540e43a524763dd53b26a066d472a97c4de37d5498170564510608250c3", -} -const seGiverKeysTvc = - "te6ccgECGgEAA9sAAgE0BgEBAcACAgPPIAUDAQHeBAAD0CAAQdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAIm/wD0pCAiwAGS9KDhiu1TWDD0oQkHAQr0pCD0oQgAAAIBIAwKAfz/fyHtRNAg10nCAZ/T/9MA9AX4an/4Yfhm+GKOG/QFbfhqcAGAQPQO8r3XC//4YnD4Y3D4Zn/4YeLTAAGOEoECANcYIPkBWPhCIPhl+RDyqN4j+EL4RSBukjBw3rry4GUh0z/THzQx+CMhAb7yuSH5ACD4SoEBAPQOIJEx3rMLAE7y4Gb4ACH4SiIBVQHIyz9ZgQEA9EP4aiMEXwTTHwHwAfhHbpLyPN4CASASDQIBWBEOAQm46Jj8UA8B/vhBbo4S7UTQ0//TAPQF+Gp/+GH4Zvhi3tFwbW8C+EqBAQD0hpUB1ws/f5NwcHDikSCONyMjI28CbyLIIs8L/yHPCz8xMQFvIiGkA1mAIPRDbwI0IvhKgQEA9HyVAdcLP3+TcHBw4gI1MzHoXwPIghB3RMfighCAAAAAsc8LHyEQAKJvIgLLH/QAyIJYYAAAAAAAAAAAAAAAAM8LZoEDmCLPMQG5lnHPQCHPF5Vxz0EhzeIgyXH7AFswwP+OEvhCyMv/+EbPCwD4SgH0AMntVN5/+GcAxbkWq+f/CC3Rxt2omgQa6ThAM/p/+mAegL8NT/8MPwzfDFHDfoCtvw1OADAIHoHeV7rhf/8MTh8Mbh8Mz/8MPFvfCNJeRnJuPwzcXwAaPwhZGX//CNnhYB8JQD6AGT2qj/8M8AIBIBUTAde7Fe+TX4QW6OEu1E0NP/0wD0Bfhqf/hh+Gb4Yt76QNcNf5XU0dDTf9/XDACV1NHQ0gDf0SIiInPIcc8LASLPCgBzz0AkzxYj+gKAac9Acs9AIMki+wBfBfhKgQEA9IaVAdcLP3+TcHBw4pEggUAJKOLfgjIgG7n/hKIwEhAYEBAPRbMDH4at4i+EqBAQD0fJUB1ws/f5NwcHDiAjUzMehfA18D+ELIy//4Rs8LAPhKAfQAye1Uf/hnAgEgFxYAx7jkYYdfCC3Rwl2omhp/+mAegL8NT/8MPwzfDFvamj8IXwikDdJGDhvXXlwMvwAfCFkZf/8I2eFgHwlAPoAZPaqfAeQfYIQaHaPdqn4ARh8IWRl//wjZ4WAfCUA+gBk9qo//DPACAtoZGAAtr4QsjL//hGzwsA+EoB9ADJ7VT4D/IAgAdacCHHAJ0i0HPXIdcLAMABkJDi4CHXDR+S8jzhUxHAAJDgwQMighD////9vLGS8jzgAfAB+EdukvI83o" - export class NetworkGiver implements AccountGiver { account: Account value: number | undefined @@ -91,25 +80,15 @@ export class NetworkGiver implements AccountGiver { client: TonClient, info: NetworkGiverInfo, ): Promise { - const signerName = (info.signer || new SignerRegistry().default) ?? "" - const signer = - signerName !== "" - ? await new SignerRegistry().resolveSigner(signerName, { - useNoneForEmptyName: true, - }) - : signerKeys(seGiverKeys) + const signerName = info.signer ?? SE_DEFAULT_GIVER_SIGNER.name + const signer = await new SignerRegistry().resolveSigner(signerName, { + useNoneForEmptyName: true, + }) + const knownName = info.name as KnownContractName const address = info.address !== "" ? info.address - : ( - await client.abi.encode_message({ - abi: abiContract(KnownContracts.GiverV2.abi), - deploy_set: { - tvc: seGiverKeysTvc, - }, - signer, - }) - ).address + : await knownContractAddress(client, knownName, signer) let contract: KnownContract let send: ( giver: Account, @@ -118,7 +97,7 @@ export class NetworkGiver implements AccountGiver { ) => Promise if (info.name !== undefined && info.name !== "auto") { - contract = await knownContractByName(info.name) + contract = await knownContractByName(knownName) } else { contract = await knownContractFromAddress(client, "Giver", address) } diff --git a/src/controllers/network/index.ts b/src/controllers/network/index.ts index 4355488..f265b55 100644 --- a/src/controllers/network/index.ts +++ b/src/controllers/network/index.ts @@ -1,6 +1,13 @@ -import { Command, CommandArg, Terminal, ToolController } from "../../core" +import { + Command, + CommandArg, + CommandArgVariant, + Terminal, + ToolController, +} from "../../core" import { getGiverSummary, NetworkRegistry } from "./registry" import { formatTable, parseNumber } from "../../core/utils" +import { KnownContractNames } from "../../core/known-contracts" const forceArg: CommandArg = { name: "force", @@ -52,15 +59,15 @@ export const networkListCommand: Command = { async run(terminal: Terminal) { const registry = new NetworkRegistry() const rows = [["Network", "Endpoints", "Giver", "Description"]] - registry.items.forEach(network => { - const summary = registry.getNetworkSummary(network) + for (const network of registry.items) { + const summary = await registry.getNetworkSummary(network) rows.push([ summary.name, summary.endpoints, summary.giver, summary.description, ]) - }) + } const table = formatTable(rows, { headerSeparator: true }) if (table.trim() !== "") { terminal.log(table) @@ -88,7 +95,10 @@ export const networkInfoCommand: Command = { rows.push(["Endpoints", network.endpoints.join(", ")]) const giver = network.giver if (giver) { - rows.push(["Giver", getGiverSummary(giver)]) + rows.push([ + "Giver", + await getGiverSummary(true, network.endpoints, giver), + ]) } if (network.name === registry.default) { rows.push(["Default", "true"]) @@ -137,7 +147,6 @@ export const networkGiverCommand: Command = { alias: "s", title: "Signer to be used with giver", type: "string", - defaultValue: "", }, { name: "value", @@ -151,7 +160,9 @@ export const networkGiverCommand: Command = { alias: "t", title: "Type giver contract (GiverV1 | GiverV2 | GiverV3 | SafeMultisigWallet | SetcodeMultisigWallet)", type: "string", - defaultValue: "auto", + getVariants(): CommandArgVariant[] | Promise { + return KnownContractNames.map(x => ({ value: x })) + }, }, ], async run( @@ -181,7 +192,6 @@ export const networkCredsCommand: Command = { title: "Set credentials for network authentication", args: [ nameArg, - { name: "project", alias: "p", diff --git a/src/controllers/network/registry.ts b/src/controllers/network/registry.ts index c9dac46..e298f51 100644 --- a/src/controllers/network/registry.ts +++ b/src/controllers/network/registry.ts @@ -5,7 +5,8 @@ import { everdevHome } from "../../core" import { NetworkGiver } from "./giver" import { TonClient } from "@eversdk/core" import { KnownContracts } from "../../core/known-contracts" -import { writeJsonFile } from "../../core/utils" +import { formatTokens, writeJsonFile } from "../../core/utils" +import { SE_DEFAULT_GIVER_SIGNER } from "../signer/registry" function networkHome() { return path.resolve(everdevHome(), "network") @@ -22,7 +23,7 @@ export type NetworkGiverInfo = { value?: number } -export type MetworkCredentials = { +export type NetworkCredentials = { project?: string accessKey?: string } @@ -32,7 +33,7 @@ export type Network = { description?: string endpoints: string[] giver?: NetworkGiverInfo - credentials?: MetworkCredentials + credentials?: NetworkCredentials } type NetworkSummary = { @@ -42,12 +43,46 @@ type NetworkSummary = { description: string } -export function getGiverSummary(giver?: NetworkGiverInfo): string { +export async function getGiverSummary( + includeAccountInfo: boolean, + endpoints: string[], + giver?: NetworkGiverInfo, +): Promise { if (!giver) { return "" } const { signer, name, address } = giver - return `${address}\n${name}${signer ? ` signed by ${signer}` : ""}` + const client = new TonClient({ network: { endpoints } }) + let accountInfo + if (includeAccountInfo) { + try { + const acc = ( + await client.net.query_collection({ + collection: "accounts", + filter: { id: { eq: address } }, + result: "balance", + limit: 1, + }) + ).result[0] + if (acc) { + if (acc.balance) { + accountInfo = `\n${formatTokens(acc.balance)}` + } else { + accountInfo = `\ngiver account has an inactive state` + } + } else { + accountInfo = `\ngiver account does not exists on a network` + } + } catch { + accountInfo = `\ngiver status is not available due to network problems` + } finally { + await client.close() + } + } else { + accountInfo = "" + } + const signerInfo = signer ? ` signed by ${signer}` : "" + return `${address}\n${name}${signerInfo}${accountInfo}` } export class NetworkRegistry { @@ -78,8 +113,8 @@ export class NetworkRegistry { giver: { name: KnownContracts.GiverV2.name, address: - "0:b5e9240fc2d2f1ff8cbb1d1dee7fb7cae155e5f6320e585fcc685698994a19a5", - signer: "", + "0:ece57bcc6c530283becbbd8a3b24d3c5987cdddc3c8b7b33be6e4a6312490415", + signer: SE_DEFAULT_GIVER_SIGNER.name, }, }, { @@ -242,13 +277,17 @@ export class NetworkRegistry { return endpoints.join(", ") } - getNetworkSummary(network: Network): NetworkSummary { + async getNetworkSummary(network: Network): Promise { return { name: `${network.name}${ network.name === this.default ? " (Default)" : "" }`, endpoints: NetworkRegistry.getEndpointsSummary(network), - giver: getGiverSummary(network.giver), + giver: await getGiverSummary( + false, + network.endpoints, + network.giver, + ), description: network.description ?? "", } } diff --git a/src/controllers/se/registry.ts b/src/controllers/se/registry.ts index 738f90f..af92bb9 100644 --- a/src/controllers/se/registry.ts +++ b/src/controllers/se/registry.ts @@ -10,6 +10,7 @@ import { } from "../../core/utils" import { ContainerDef, ContainerStatus, DevDocker } from "../../core/docker" import Dockerode from "dockerode" +import { NetworkRegistry } from "../network/registry" const DEFAULT_INSTANCE_NAME = "default" const DEFAULT_INSTANCE_PORT = 80 @@ -120,10 +121,18 @@ function mapContainerName(name: string): string { return name.startsWith("/") ? name.substr(1) : name } +function getEndpoint(instance: SERegistryItem): string { + return (instance.port || 80) === 80 + ? "http://localhost" + : `http://localhost:${instance.port}` +} + export function updateInstance( + networks: NetworkRegistry, instance: SERegistryItem, updates: Partial, ) { + const oldEndpoint = getEndpoint(instance) if ( updates.source === undefined && updates.port === undefined && @@ -145,6 +154,16 @@ export function updateInstance( } else if (updates.dbPort !== undefined) { instance.dbPort = updates.dbPort } + const newEndpoint = getEndpoint(instance) + if (newEndpoint !== oldEndpoint) { + for (const network of networks.items) { + for (let i = 0; i < network.endpoints.length; i += 1) { + if (network.endpoints[i] === oldEndpoint) { + network.endpoints[i] = newEndpoint + } + } + } + } } export class SERegistry { @@ -425,12 +444,13 @@ export class SERegistry { } async update(terminal: Terminal, instance: string) { + const networks = new NetworkRegistry() await this.updateConfig( terminal, instance, async instance => { if (instance.source.type === SESourceType.TONOS_SE_VERSION) { - updateInstance(instance, { + updateInstance(networks, instance, { source: seSourceVersion( await SERegistry.getLatestVersion(), ), @@ -500,12 +520,14 @@ export class SERegistry { } } } + const networks = new NetworkRegistry() await this.updateConfig( terminal, args.instance, - async x => updateInstance(x, updates), + async x => updateInstance(networks, x, updates), true, ) + networks.save() } delete(instance: string, force: boolean) { diff --git a/src/controllers/signer/registry.ts b/src/controllers/signer/registry.ts index 4a34af8..cad3ca4 100644 --- a/src/controllers/signer/registry.ts +++ b/src/controllers/signer/registry.ts @@ -25,6 +25,15 @@ function registryPath() { return path.resolve(signerHome(), "registry.json") } +export const SE_DEFAULT_GIVER_SIGNER: SignerRegistryItem = { + name: "seGiver", + description: "EverNode SE Default Giver Keys", + keys: { + public: "2ada2e65ab8eeab09490e3521415f45b6e42df9c760a639bcf53957550b25a16", + secret: "172af540e43a524763dd53b26a066d472a97c4de37d5498170564510608250c3", + }, +} + export enum MnemonicDictionary { ton = 0, english = 1, @@ -72,6 +81,9 @@ export class SignerRegistry { this.default = loaded.default } catch {} /* eslint-disable-line no-empty */ } + if (!this.items.find(x => x.name === SE_DEFAULT_GIVER_SIGNER.name)) { + this.items.push(SE_DEFAULT_GIVER_SIGNER) + } } save() { diff --git a/src/controllers/solidity/index.ts b/src/controllers/solidity/index.ts index 2257d0a..b14b0b1 100644 --- a/src/controllers/solidity/index.ts +++ b/src/controllers/solidity/index.ts @@ -16,6 +16,29 @@ export const SOLIDITY_FILE = defineFileType("Solidity", /\.t?sol$/i, [ ".tsol", ]) +function resolveSoliditySource(file: string): { + fileDir: string + fileName: string +} { + const fileDir = path.dirname(file) + let fileName = path.basename(file) + const exists = (name: string): boolean => { + return fs.existsSync(path.resolve(fileDir, name)) + } + if (!exists(fileName)) { + for (const ext of SOLIDITY_FILE.extensions) { + fileName = changeExt(fileName, ext) + if (exists(fileName)) { + break + } + } + if (!exists(fileName)) { + throw new Error(`Solidity source file ${file} does not exist.`) + } + } + return { fileDir, fileName } +} + export const solidityVersionCommand: Command = { name: "version", title: "Show Solidity Version", @@ -97,9 +120,7 @@ export const solidityCompileCommand: Command = { ): Promise { await Component.ensureInstalledAll(terminal, components) for (const file of args.file.split(" ")) { - const fileDir = path.dirname(file) - const fileName = path.basename(file) - + const { fileDir, fileName } = resolveSoliditySource(file) const outputDir = path.resolve(args.outputDir ?? ".") const includePath = args.includePath ? args.includePath @@ -226,18 +247,12 @@ export const solidityAstCommand: Command = { }, ): Promise { for (const file of args.file.split(" ")) { - const ext = path.extname(file) - if (SOLIDITY_FILE.extensions.includes(ext) === false) { - terminal.log(`Choose solidity source file.`) - return - } if (args.format.match(/^(compact-json|json)$/i) == null) { terminal.log(`Wrong ast format.`) return } await Component.ensureInstalledAll(terminal, components) - const fileDir = path.dirname(file) - const fileName = path.basename(file) + const { fileDir, fileName } = resolveSoliditySource(file) args.outputDir = path.resolve(args.outputDir ?? ".") const includePath = args.includePath ? args.includePath diff --git a/src/core/known-contracts.ts b/src/core/known-contracts.ts index 977127c..4ab6c23 100644 --- a/src/core/known-contracts.ts +++ b/src/core/known-contracts.ts @@ -1,8 +1,9 @@ import { Account, ContractPackage } from "@eversdk/appkit" -import { TonClient, AbiContract } from "@eversdk/core" +import { TonClient, AbiContract, Signer, abiContract } from "@eversdk/core" import path from "path" import fs from "fs" +import { getParsedAccount } from "../controllers/contract/accounts" export type KnownContract = { name: string @@ -13,13 +14,15 @@ export async function knownContractFromAddress( name: string, address: string, ): Promise { - const info = await new Account( - { abi: {} }, - { - client, - address, - }, - ).getAccount() + const info = await getParsedAccount( + await new Account( + { abi: {} }, + { + client, + address, + }, + ), + ) const codeHash = info.code_hash if (!codeHash) { throw new Error(`${name} ${address} has no code deployed.`) @@ -27,7 +30,29 @@ export async function knownContractFromAddress( return knownContractFromCodeHash(codeHash, name, address) } -export function knownContractByName(name: string): KnownContract { +export async function knownContractAddress( + client: TonClient, + name: KnownContractName, + signer: Signer, +): Promise { + const contract = KnownContracts[name] + if (!contract || !contract.tvc) { + throw new Error( + `Can not resolve giver address: unknown giver type ${name}.`, + ) + } + return ( + await client.abi.encode_message({ + abi: abiContract(contract.abi), + deploy_set: { + tvc: contract.tvc, + }, + signer, + }) + ).address +} + +export function knownContractByName(name: KnownContractName): KnownContract { if (!(name in KnownContracts)) { throw new Error(`Unknown contract type ${name}.`) } @@ -39,7 +64,7 @@ export function knownContractFromCodeHash( name: string, address?: string, ): KnownContract { - const contract = contracts[codeHash] + const contract = KnownContractsByCodeHash[codeHash] if (!contract) { if (address) { throw new Error( @@ -61,30 +86,33 @@ export function loadAbi(name: string): AbiContract { ) } -export const KnownContracts: { [key: string]: KnownContract } = { - GiverV1: { - name: "GiverV1", - abi: loadAbi("GiverV1"), - }, - GiverV2: { - name: "GiverV2", - abi: loadAbi("GiverV2"), - }, - GiverV3: { - name: "GiverV3", - abi: loadAbi("GiverV3"), - }, - SetcodeMultisigWallet: { - name: "SetcodeMultisigWallet", - abi: loadAbi("SetcodeMultisigWallet"), - }, - SafeMultisigWallet: { - name: "SafeMultisigWallet", - abi: loadAbi("SafeMultisigWallet"), - }, +export function loadTvc(name: string): string | undefined { + const filePath = contractsFile(`${name}.tvc`) + return fs.existsSync(filePath) + ? fs.readFileSync(filePath, "base64") + : undefined } -const contracts: { [codeHash: string]: KnownContract } = { +function knownContract(name: string): KnownContract { + return { + name, + abi: loadAbi(name), + tvc: loadTvc(name), + } +} + +export const KnownContracts = { + GiverV1: knownContract("GiverV1"), + GiverV2: knownContract("GiverV2"), + GiverV3: knownContract("GiverV3"), + SetcodeMultisigWallet: knownContract("SetcodeMultisigWallet"), + SafeMultisigWallet: knownContract("SafeMultisigWallet"), +} + +export type KnownContractName = keyof typeof KnownContracts +export const KnownContractNames: string[] = Object.keys(KnownContracts) + +const KnownContractsByCodeHash: { [codeHash: string]: KnownContract } = { "4e92716de61d456e58f16e4e867e3e93a7548321eace86301b51c8b80ca6239b": KnownContracts.GiverV2, ccbfc821853aa641af3813ebd477e26818b51e4ca23e5f6d34509215aa7123d9: diff --git a/src/core/solFileResolvers.ts b/src/core/solFileResolvers.ts index d0a3cd1..042814d 100644 --- a/src/core/solFileResolvers.ts +++ b/src/core/solFileResolvers.ts @@ -2,6 +2,7 @@ import fs from "fs" import { ContractPackage } from "@eversdk/appkit" import { findExisting } from "./utils" import { SOLIDITY_FILE } from "../controllers/solidity" +import os from "os" type ResolvedContractPackage = { package: ContractPackage @@ -10,6 +11,9 @@ type ResolvedContractPackage = { } export function resolveContract(filePath: string): ResolvedContractPackage { + if (filePath.startsWith("~")) { + filePath = `${os.homedir()}${filePath.substring(1)}` + } filePath = filePath.trim() const lowered = filePath.toLowerCase() let basePath @@ -35,7 +39,9 @@ export function resolveContract(filePath: string): ResolvedContractPackage { ? JSON.parse(fs.readFileSync(abiPath, "utf8")) : undefined if (!abi) { - throw new Error("ABI file missing.") + throw new Error( + `You have specified "${filePath}" as a contract file name, but a corresponding ABI file is missing. ABI file must have an extension ".abi" or ".abi.json".`, + ) } return { package: { diff --git a/src/core/utils.ts b/src/core/utils.ts index 0a02d73..c239931 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -33,7 +33,8 @@ export function executableName(name: string): string { } export function changeExt(path: string, newExt: string): string { - return path.replace(/\.[^/.]+$/, newExt) + const changed = path.replace(/\.[^/.]+$/, newExt) + return changed === path ? `${path}${newExt}` : changed } export function ellipsisString(xs: string[]): string {