diff --git a/.gitignore b/.gitignore index 5cd6bd8..f71038c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules dist +.idea # yarn .yarn/* diff --git a/examples/contracts/Elector.ts b/examples/contracts/Elector.ts index c7fba7c..49b9230 100644 --- a/examples/contracts/Elector.ts +++ b/examples/contracts/Elector.ts @@ -1,7 +1,7 @@ -import { Address, Cell, Contract, ContractProvider } from "@ton/core"; +import {Address, Contract, ContractProvider, StateInit} from "@ton/core"; export class Elector implements Contract { - constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} + constructor(readonly address: Address, readonly init?: StateInit) {} static createFromAddress(address: Address) { return new Elector(address); diff --git a/examples/contracts/NftCollection.ts b/examples/contracts/NftCollection.ts index 7a78a1b..15a26a1 100644 --- a/examples/contracts/NftCollection.ts +++ b/examples/contracts/NftCollection.ts @@ -1,4 +1,14 @@ -import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, toNano } from "@ton/core"; +import { + Address, + beginCell, + Cell, + Contract, + contractAddress, + ContractProvider, + Sender, + StateInit, + toNano +} from "@ton/core"; import { NftItem } from "./NftItem"; export type NftCollectionData = { @@ -30,7 +40,7 @@ export class NftCollection implements Contract { nextItemIndex: number = 0; - constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} + constructor(readonly address: Address, readonly init?: StateInit) {} static createFromAddress(address: Address) { return new NftCollection(address); @@ -80,4 +90,4 @@ export class NftCollection implements Contract { owner: stack.readAddress(), } } -} \ No newline at end of file +} diff --git a/examples/contracts/NftItem.ts b/examples/contracts/NftItem.ts index 7aa41cd..8d61f33 100644 --- a/examples/contracts/NftItem.ts +++ b/examples/contracts/NftItem.ts @@ -1,4 +1,4 @@ -import { Address, beginCell, Cell, Contract, ContractProvider, Sender, toNano, Builder } from "@ton/core"; +import {Address, beginCell, Cell, Contract, ContractProvider, Sender, toNano, Builder, StateInit} from "@ton/core"; export type NftItemData = { inited: boolean @@ -11,7 +11,7 @@ export type NftItemData = { export class NftItem implements Contract { static readonly code = Cell.fromBase64('te6ccgECDgEAAdwAART/APSkE/S88sgLAQIBYgIDAgLOBAUACaEfn+AFAgEgBgcCASAMDQLPDIhxwCSXwPg0NMDAXGwkl8D4PpA+kAx+gAxcdch+gAx+gAwc6m0APACBLOOFDBsIjRSMscF8uGVAfpA1DAQI/AD4AbTH9M/ghBfzD0UUjC64wIwNDQ1NYIQL8smohK64wJfBIQP8vCAICQARPpEMHC68uFNgAqwyEDdeMkATUTXHBfLhkfpAIfAB+kDSADH6ACDXScIA8uLEggr68IAboSGUUxWgod4i1wsBwwAgkgahkTbiIML/8uGSIZQQKjdb4w0CkzAyNOMNVQLwAwoLAHJwghCLdxc1BcjL/1AEzxYQJIBAcIAQyMsFUAfPFlAF+gIVy2oSyx/LPyJus5RYzxcBkTLiAckB+wAAfIIQBRONkchQCc8WUAvPFnEkSRRURqBwgBDIywVQB88WUAX6AhXLahLLH8s/Im6zlFjPFwGRMuIByQH7ABBHAGom8AGCENUydtsQN0QAbXFwgBDIywVQB88WUAX6AhXLahLLH8s/Im6zlFjPFwGRMuIByQH7AAA7O1E0NM/+kAg10nCAJp/AfpA1DAQJBAj4DBwWW1tgAB0A8jLP1jPFgHPFszJ7VSA=') - constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} + constructor(readonly address: Address, readonly init?: StateInit) {} static createFromAddress(address: Address) { return new NftItem(address); @@ -48,4 +48,4 @@ export class NftItem implements Contract { content: stack.readCellOpt(), } } -} \ No newline at end of file +} diff --git a/examples/contracts/NftMarketplace.ts b/examples/contracts/NftMarketplace.ts index 5fdded8..8d81bc9 100644 --- a/examples/contracts/NftMarketplace.ts +++ b/examples/contracts/NftMarketplace.ts @@ -1,4 +1,15 @@ -import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, storeStateInit, toNano } from "@ton/core"; +import { + Address, + beginCell, + Cell, + Contract, + contractAddress, + ContractProvider, + Sender, + StateInit, + storeStateInit, + toNano +} from "@ton/core"; export type NftMarketplaceConfig = { owner: Address @@ -13,7 +24,7 @@ function nftMarketplaceConfigToCell(config: NftMarketplaceConfig): Cell { export class NftMarketplace implements Contract { static readonly code = Cell.fromBase64('te6ccgEBBAEAagABFP8A9KQT9LzyyAsBAgEgAgMApNIyIccAkVvg0NMDAXGwkVvg+kAw7UTQ+kAwxwXy4ZHTHwHAAY4p+gDU1DAh+QBwyMoHy//J0Hd0gBjIywXLAljPFlAE+gITy2vMzMlx+wCRMOIABPIw') - constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} + constructor(readonly address: Address, readonly init?: StateInit) {} static createFromAddress(address: Address) { return new NftMarketplace(address); @@ -26,7 +37,7 @@ export class NftMarketplace implements Contract { } async sendDeploy(provider: ContractProvider, via: Sender, params: { - init: { code: Cell, data: Cell } + init: StateInit body?: Cell value?: bigint deployValue?: bigint @@ -42,4 +53,4 @@ export class NftMarketplace implements Contract { }) return contractAddress(0, params.init) } -} \ No newline at end of file +} diff --git a/examples/contracts/NftSale.ts b/examples/contracts/NftSale.ts index f31bd24..8b4d106 100644 --- a/examples/contracts/NftSale.ts +++ b/examples/contracts/NftSale.ts @@ -1,4 +1,4 @@ -import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider } from "@ton/core"; +import {Address, beginCell, Cell, Contract, contractAddress, ContractProvider, StateInit} from "@ton/core"; export type NftSaleConfig = { marketplace: Address @@ -26,7 +26,7 @@ function nftSaleConfigToCell(config: NftSaleConfig): Cell { export class NftSale implements Contract { static readonly code = Cell.fromBase64('te6ccgECCgEAAbQAART/APSkE/S88sgLAQIBIAIDAgFIBAUABPIwAgLNBgcAL6A4WdqJofSB9IH0gfQBqGGh9AH0gfQAYQH30A6GmBgLjYSS+CcH0gGHaiaH0gfSB9IH0AahgRa6ThAVnHHZkbGymQ44LJL4NwKJFjgvlw+gFpj8EIAonGyIldeXD66Z+Y/SAYIBpkKALniygB54sA54sA/QFmZPaqcBNjgEybCBsimYI4eAJwA2mP6Z+YEOAAyS+FcBDAgB9dQQgdzWUAKShQKRhfeXDhAeh9AH0gfQAYOEAIZGWCqATniyi50JDQqFrQilAK/QEK5bVkuP2AOEAIZGWCrGeLKAP9AQtltWS4/YA4QAhkZYKoAueLAP0BCeW1ZLj9gDgQQQgv5h6KEMAMZGWCqALnixF9AQpltQnlj4pAkAyMACmjEQRxA2RUAS8ATgMjY3BMADjkeCEDuaygAVvvLhyVMSxwVZxwWx8uHKcCCCEF/MPRQhgBDIywVQBs8WIvoCFctqFMsfFMs/Ic8WAc8WygAh+gLKAMmBAKD7AOBfBoQP8vAAKss/Is8WWM8WygAh+gLKAMmBAKD7AA==') - constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} + constructor(readonly address: Address, readonly init?: StateInit) {} static createFromAddress(address: Address) { return new NftSale(address); @@ -50,4 +50,4 @@ export class NftSale implements Contract { royaltyAmount: stack.readBigNumber(), } } -} \ No newline at end of file +} diff --git a/examples/contracts/NftSaleV3.ts b/examples/contracts/NftSaleV3.ts index 1405381..b5f883d 100644 --- a/examples/contracts/NftSaleV3.ts +++ b/examples/contracts/NftSaleV3.ts @@ -1,8 +1,8 @@ -import { Address, Cell, Contract, ContractProvider } from "@ton/core"; +import {Address, Contract, ContractProvider, StateInit} from "@ton/core"; import { NftSaleConfig } from "./NftSale"; export class NftSaleV3 implements Contract { - constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} + constructor(readonly address: Address, readonly init?: StateInit) {} static createFromAddress(address: Address) { return new NftSaleV3(address); @@ -33,4 +33,4 @@ export class NftSaleV3 implements Contract { royaltyAmount, } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 8f25919..47938d8 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,10 @@ "url": "git+https://github.com/ton-org/sandbox" }, "devDependencies": { - "@ton/core": "^0.49.2", + "@ton/core": "^0.56.0", "@ton/crypto": "3.2.0", "@ton/test-utils": "^0.3.1", - "@ton/ton": "^13.4.2", + "@ton/ton": "^13.11.0", "@types/jest": "^29.5.0", "@types/node": "^18.15.11", "jest": "^29.5.0", @@ -25,7 +25,7 @@ "typescript": "^4.9.5" }, "peerDependencies": { - "@ton/core": ">=0.49.2", + "@ton/core": ">=0.56.0", "@ton/crypto": ">=3.2.0" }, "scripts": { diff --git a/src/blockchain/Blockchain.ts b/src/blockchain/Blockchain.ts index d11b974..857de5b 100644 --- a/src/blockchain/Blockchain.ts +++ b/src/blockchain/Blockchain.ts @@ -1,5 +1,20 @@ import { defaultConfig } from "../config/defaultConfig"; -import {Address, Cell, Message, Transaction, ContractProvider, Contract, Sender, toNano, loadMessage, ShardAccount, TupleItem, ExternalAddress, StateInit} from "@ton/core"; +import { + Address, + Cell, + Message, + Transaction, + ContractProvider, + Contract, + Sender, + toNano, + loadMessage, + ShardAccount, + TupleItem, + ExternalAddress, + StateInit, + OpenedContract +} from "@ton/core"; import {Executor, TickOrTock} from "../executor/Executor"; import {BlockchainStorage, LocalBlockchainStorage} from "./BlockchainStorage"; import { extractEvents, Event } from "../event/Event"; @@ -52,6 +67,8 @@ export type SendMessageResult = { type ExtendsContractProvider = T extends ContractProvider ? true : (T extends SandboxContractProvider ? true : false); +export const SANDBOX_CONTRACT_SYMBOL = Symbol('SandboxContract') + export type SandboxContract = { [P in keyof F]: P extends `get${string}` ? (F[P] extends (x: infer CP, ...args: infer P) => infer R ? (ExtendsContractProvider extends true ? (...args: P) => R : never) : never) @@ -62,6 +79,14 @@ export type SandboxContract = { : F[P]); } +export function toSandboxContract(contract: OpenedContract): SandboxContract { + if ((contract as any)[SANDBOX_CONTRACT_SYMBOL] === true) { + return contract as any + } + + throw new Error('Invalid contract: not a sandbox contract') +} + export type PendingMessage = (({ type: 'message', } & Message) | ({ @@ -329,12 +354,13 @@ export class Blockchain { }) } - provider(address: Address, init?: { code: Cell, data: Cell }): ContractProvider { + provider(address: Address, init?: StateInit | null): ContractProvider { return new BlockchainContractProvider({ getContract: (addr) => this.getContract(addr), pushMessage: (msg) => this.pushMessage(msg), runGetMethod: (addr, method, args) => this.runGetMethod(addr, method, args), pushTickTock: (on, which) => this.pushTickTock(on, which), + openContract: (contract: T) => this.openContract(contract) as OpenedContract, }, address, init) } @@ -379,7 +405,7 @@ export class Blockchain { openContract(contract: T) { let address: Address; - let init: { code: Cell, data: Cell } | undefined = undefined; + let init: StateInit | undefined = undefined; if (!Address.isAddress(contract.address)) { throw Error('Invalid address'); @@ -400,6 +426,10 @@ export class Blockchain { return new Proxy(contract as any, { get(target, prop) { + if (prop === SANDBOX_CONTRACT_SYMBOL) { + return true + } + const value = target[prop] if (typeof prop === 'string' && typeof value === 'function') { if (prop.startsWith('get')) { @@ -486,4 +516,4 @@ export class Blockchain { ...opts }) } -} \ No newline at end of file +} diff --git a/src/blockchain/BlockchainContractProvider.ts b/src/blockchain/BlockchainContractProvider.ts index 316a168..cf26c87 100644 --- a/src/blockchain/BlockchainContractProvider.ts +++ b/src/blockchain/BlockchainContractProvider.ts @@ -1,11 +1,23 @@ -import { AccountState, Address, Cell, comment, ContractGetMethodResult, ContractProvider, ContractState, Message, Sender, SendMode, toNano, TupleItem } from "@ton/core"; -import { TickOrTock } from "../executor/Executor"; -import { GetMethodResult, SmartContract } from "./SmartContract"; +import { + AccountState, Address, Cell, comment, Contract, + ContractGetMethodResult, + ContractProvider, + ContractState, + Message, openContract, + OpenedContract, + Sender, + SendMode, StateInit, + toNano, + Transaction, + TupleItem +} from "@ton/core"; +import {TickOrTock} from "../executor/Executor"; +import {GetMethodResult, SmartContract} from "./SmartContract"; function bigintToBuffer(x: bigint, n = 32): Buffer { const b = Buffer.alloc(n) for (let i = 0; i < n; i++) { - b[n-i-1] = Number((x >> BigInt(i * 8)) & 0xffn) + b[n - i - 1] = Number((x >> BigInt(i * 8)) & 0xffn) } return b } @@ -44,12 +56,16 @@ export class BlockchainContractProvider implements SandboxContractProvider { getContract(address: Address): Promise pushMessage(message: Message): Promise runGetMethod(address: Address, method: string, args: TupleItem[]): Promise - pushTickTock(on: Address, which: TickOrTock): Promise + pushTickTock(on: Address, which: TickOrTock): Promise, + openContract(contract: T): OpenedContract }, private readonly address: Address, - private readonly init?: { code: Cell, data: Cell }, + private readonly init?: StateInit | null, ) {} + open(contract: T): OpenedContract { + return this.blockchain.openContract(contract); + } async getState(): Promise { const contract = await this.blockchain.getContract(this.address) return { @@ -72,6 +88,9 @@ export class BlockchainContractProvider implements SandboxContractProvider { delete (ret as any).stackReader return ret } + getTransactions(address: Address, lt: bigint, hash: Buffer, limit?: number | undefined): Promise { + throw new Error("`getTransactions` is not implemented in `BlockchainContractProvider`, do not use it in the tests") + } async external(message: Cell) { const init = ((await this.getState()).state.type !== 'active' && this.init) ? this.init : undefined @@ -106,4 +125,4 @@ export class BlockchainContractProvider implements SandboxContractProvider { async tickTock(which: TickOrTock) { await this.blockchain.pushTickTock(this.address, which) } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 43a99e2..4c6cf81 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ export { export { Blockchain, + toSandboxContract, SendMessageResult, BlockchainTransaction, PendingMessage, @@ -15,6 +16,10 @@ export { BlockchainSnapshot, } from './blockchain/Blockchain'; +export { + BlockchainApi +} from './blockchain/BlockchainApi'; + export { BlockchainContractProvider, SandboxContractProvider, @@ -75,4 +80,4 @@ export { export { internal, -} from './utils/message'; \ No newline at end of file +} from './utils/message'; diff --git a/src/treasury/Treasury.ts b/src/treasury/Treasury.ts index 1d88d5e..46fd41f 100644 --- a/src/treasury/Treasury.ts +++ b/src/treasury/Treasury.ts @@ -1,4 +1,21 @@ -import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Dictionary, DictionaryValue, internal, loadMessageRelaxed, MessageRelaxed, Sender, SenderArguments, SendMode, storeMessageRelaxed } from "@ton/core"; +import { + Address, + beginCell, + Cell, + Contract, + contractAddress, + ContractProvider, + Dictionary, + DictionaryValue, + internal, + loadMessageRelaxed, + MessageRelaxed, + Sender, + SenderArguments, + SendMode, + StateInit, + storeMessageRelaxed +} from "@ton/core"; const DictionaryMessageValue: DictionaryValue<{ sendMode: SendMode, message: MessageRelaxed }> = { serialize(src, builder) { @@ -34,7 +51,7 @@ export class TreasuryContract implements Contract { } readonly address: Address; - readonly init: { code: Cell, data: Cell }; + readonly init: StateInit; readonly subwalletId: bigint; constructor(workchain: number, subwalletId: bigint) { diff --git a/yarn.lock b/yarn.lock index fd72ddf..a58507d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -789,14 +789,14 @@ __metadata: languageName: node linkType: hard -"@ton/core@npm:^0.49.2": - version: 0.49.2 - resolution: "@ton/core@npm:0.49.2" +"@ton/core@npm:^0.56.0": + version: 0.56.0 + resolution: "@ton/core@npm:0.56.0" dependencies: symbol.inspect: 1.0.1 peerDependencies: "@ton/crypto": ">=3.2.0" - checksum: c0bbad44f89664852d2289e073fa5666d38b4738f13cb951b973bd56b635df6d45ee26c6d1d6348c8736172fd84aaa8f5ef3296f45cca2b346c715a92a0cf774 + checksum: ce7540c8ee079908a756d789833a672710251a5543f2af00122b965d21f31de2d5d4fa0d2ce232d531ae772919fbc65ec0ca498b475a042f99e7738f80590872 languageName: node linkType: hard @@ -824,10 +824,10 @@ __metadata: version: 0.0.0-use.local resolution: "@ton/sandbox@workspace:." dependencies: - "@ton/core": ^0.49.2 + "@ton/core": ^0.56.0 "@ton/crypto": 3.2.0 "@ton/test-utils": ^0.3.1 - "@ton/ton": ^13.4.2 + "@ton/ton": ^13.11.0 "@types/jest": ^29.5.0 "@types/node": ^18.15.11 jest: ^29.5.0 @@ -835,7 +835,7 @@ __metadata: ts-node: ^10.9.1 typescript: ^4.9.5 peerDependencies: - "@ton/core": ">=0.49.2" + "@ton/core": ">=0.56.0" "@ton/crypto": ">=3.2.0" languageName: unknown linkType: soft @@ -858,9 +858,9 @@ __metadata: languageName: node linkType: hard -"@ton/ton@npm:^13.4.2": - version: 13.5.1 - resolution: "@ton/ton@npm:13.5.1" +"@ton/ton@npm:^13.11.0": + version: 13.11.0 + resolution: "@ton/ton@npm:13.11.0" dependencies: axios: ^0.25.0 dataloader: ^2.0.0 @@ -868,9 +868,9 @@ __metadata: teslabot: ^1.3.0 zod: ^3.21.4 peerDependencies: - "@ton/core": ">=0.49.2" + "@ton/core": ">=0.56.0" "@ton/crypto": ">=3.2.0" - checksum: a9ce1ddcd513a77b8d610700d758c485bd970d40c8a34bc341c7b518714e1789e04aa9e41078cae4a6082a0b69baccae4a4d7d9b0d35312c2a4c262be48a409a + checksum: 21484bb4828d6b7d35c0fa8bd6131218b5200782f85fd3c285bf8e1ba71c8c8e9fc9f69d7531f5f8f715b8eb15fdfdae63dba5cf1fc94d75a5919860e24290d3 languageName: node linkType: hard