Skip to content

Commit

Permalink
Add open and getTransactions to ContractProvider
Browse files Browse the repository at this point in the history
Add helper `toSandboxContract` function
  • Loading branch information
thekiba authored Mar 1, 2024
1 parent 69e8885 commit 8bc68c2
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 47 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules
dist
.idea

# yarn
.yarn/*
Expand Down
4 changes: 2 additions & 2 deletions examples/contracts/Elector.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
16 changes: 13 additions & 3 deletions examples/contracts/NftCollection.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -80,4 +90,4 @@ export class NftCollection implements Contract {
owner: stack.readAddress(),
}
}
}
}
6 changes: 3 additions & 3 deletions examples/contracts/NftItem.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -48,4 +48,4 @@ export class NftItem implements Contract {
content: stack.readCellOpt(),
}
}
}
}
19 changes: 15 additions & 4 deletions examples/contracts/NftMarketplace.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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);
Expand All @@ -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
Expand All @@ -42,4 +53,4 @@ export class NftMarketplace implements Contract {
})
return contractAddress(0, params.init)
}
}
}
6 changes: 3 additions & 3 deletions examples/contracts/NftSale.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -50,4 +50,4 @@ export class NftSale implements Contract {
royaltyAmount: stack.readBigNumber(),
}
}
}
}
6 changes: 3 additions & 3 deletions examples/contracts/NftSaleV3.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -33,4 +33,4 @@ export class NftSaleV3 implements Contract {
royaltyAmount,
}
}
}
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
38 changes: 34 additions & 4 deletions src/blockchain/Blockchain.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -52,6 +67,8 @@ export type SendMessageResult = {

type ExtendsContractProvider<T> = T extends ContractProvider ? true : (T extends SandboxContractProvider ? true : false);

export const SANDBOX_CONTRACT_SYMBOL = Symbol('SandboxContract')

export type SandboxContract<F> = {
[P in keyof F]: P extends `get${string}`
? (F[P] extends (x: infer CP, ...args: infer P) => infer R ? (ExtendsContractProvider<CP> extends true ? (...args: P) => R : never) : never)
Expand All @@ -62,6 +79,14 @@ export type SandboxContract<F> = {
: F[P]);
}

export function toSandboxContract<T>(contract: OpenedContract<T>): SandboxContract<T> {
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) | ({
Expand Down Expand Up @@ -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: <T extends Contract>(contract: T) => this.openContract(contract) as OpenedContract<T>,
}, address, init)
}

Expand Down Expand Up @@ -379,7 +405,7 @@ export class Blockchain {

openContract<T extends Contract>(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');
Expand All @@ -400,6 +426,10 @@ export class Blockchain {

return new Proxy<any>(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')) {
Expand Down Expand Up @@ -486,4 +516,4 @@ export class Blockchain {
...opts
})
}
}
}
33 changes: 26 additions & 7 deletions src/blockchain/BlockchainContractProvider.ts
Original file line number Diff line number Diff line change
@@ -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
}
Expand Down Expand Up @@ -44,12 +56,16 @@ export class BlockchainContractProvider implements SandboxContractProvider {
getContract(address: Address): Promise<SmartContract>
pushMessage(message: Message): Promise<void>
runGetMethod(address: Address, method: string, args: TupleItem[]): Promise<GetMethodResult>
pushTickTock(on: Address, which: TickOrTock): Promise<void>
pushTickTock(on: Address, which: TickOrTock): Promise<void>,
openContract<T extends Contract>(contract: T): OpenedContract<T>
},
private readonly address: Address,
private readonly init?: { code: Cell, data: Cell },
private readonly init?: StateInit | null,
) {}

open<T extends Contract>(contract: T): OpenedContract<T> {
return this.blockchain.openContract(contract);
}
async getState(): Promise<ContractState> {
const contract = await this.blockchain.getContract(this.address)
return {
Expand All @@ -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<Transaction[]> {
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

Expand Down Expand Up @@ -106,4 +125,4 @@ export class BlockchainContractProvider implements SandboxContractProvider {
async tickTock(which: TickOrTock) {
await this.blockchain.pushTickTock(this.address, which)
}
}
}
7 changes: 6 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export {

export {
Blockchain,
toSandboxContract,
SendMessageResult,
BlockchainTransaction,
PendingMessage,
Expand All @@ -15,6 +16,10 @@ export {
BlockchainSnapshot,
} from './blockchain/Blockchain';

export {
BlockchainApi
} from './blockchain/BlockchainApi';

Check failure on line 21 in src/index.ts

View workflow job for this annotation

GitHub Actions / Publish

Cannot find module './blockchain/BlockchainApi' or its corresponding type declarations.

export {
BlockchainContractProvider,
SandboxContractProvider,
Expand Down Expand Up @@ -75,4 +80,4 @@ export {

export {
internal,
} from './utils/message';
} from './utils/message';
21 changes: 19 additions & 2 deletions src/treasury/Treasury.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 8bc68c2

Please sign in to comment.