feat: save source through bundlr (#283)

This commit is contained in:
Asia
2022-12-02 16:51:20 +01:00
committed by GitHub
parent 1e96130331
commit 231fefa13c
13 changed files with 271 additions and 56 deletions

View File

@@ -120,7 +120,8 @@ describe('Testing the Profit Sharing Token', () => {
const newSource = fs.readFileSync(path.join(__dirname, '../data/token-evolve.js'), 'utf8');
const newSrcTxId = await pst.save({ src: newSource }, warp.environment);
const srcTx = await warp.createSourceTx({ src: newSource }, wallet);
const newSrcTxId = await warp.saveSourceTx(srcTx);
await mineBlock(warp);
await pst.evolve(newSrcTxId);

View File

@@ -159,13 +159,15 @@ describe('Testing the Warp client for AssemblyScript WASM contract', () => {
const newContractSrc = fs.readFileSync(path.join(__dirname, '../data/wasm/as/assemblyscript-counter-evolve.wasm'));
const newSrcTxId = await contract.save(
const srcTx = await warp.createSourceTx(
{
src: newContractSrc,
wasmSrcCodeDir: path.join(__dirname, '../data/wasm/as/assembly-evolve')
},
warp.environment
wallet
);
const newSrcTxId = await warp.saveSourceTx(srcTx);
await mineBlock(warp);
await contract.evolve(newSrcTxId);

View File

@@ -197,13 +197,14 @@ describe('Testing the Go WASM Profit Sharing Token', () => {
const newContractSrc = fs.readFileSync(path.join(__dirname, '../data/wasm/go/go-pst-evolve.wasm'));
const newSrcTxId = await pst.save(
const srcTx = await warp.createSourceTx(
{
src: newContractSrc,
wasmSrcCodeDir: path.join(__dirname, '../data/wasm/go/src-evolve')
},
warp.environment
wallet
);
const newSrcTxId = await warp.saveSourceTx(srcTx);
await mineBlock(warp);

View File

@@ -223,14 +223,15 @@ describe('Testing the Rust WASM Profit Sharing Token', () => {
const newContractSrc = fs.readFileSync(path.join(__dirname, '../data/wasm/rust/rust-pst-evolve_bg.wasm'));
const newSrcTxId = await pst.save(
const srcTx = await warp.createSourceTx(
{
src: newContractSrc,
wasmSrcCodeDir: path.join(__dirname, '../data/wasm/rust/src-evolve'),
wasmGlueCode: path.join(__dirname, '../data/wasm/rust/rust-pst-evolve.js')
},
warp.environment
wallet
);
const newSrcTxId = await warp.saveSourceTx(srcTx);
await mineBlock(warp);

View File

@@ -4,7 +4,6 @@ import { InteractionResult } from '../core/modules/impl/HandlerExecutorFactory';
import { EvaluationOptions, EvalStateResult } from '../core/modules/StateEvaluator';
import { GQLNodeInterface } from '../legacy/gqlResult';
import { ArTransfer, Tags, ArWallet } from './deploy/CreateContract';
import { Source } from './deploy/Source';
import { SignatureType } from './Signature';
export type CurrentTx = { interactionTxId: string; contractTxId: string };
@@ -69,7 +68,7 @@ export type InnerCallData = { callingInteraction: GQLNodeInterface; callType: In
* A base interface to be implemented by SmartWeave Contracts clients
* - contains "low-level" methods that allow to interact with any contract
*/
export interface Contract<State = unknown> extends Source {
export interface Contract<State = unknown> {
/**
* Returns the Arweave transaction id of this contract.
*/

View File

@@ -754,17 +754,6 @@ export class HandlerBasedContract<State> implements Contract<State> {
return await this.writeInteraction<any>({ function: 'evolve', value: newSrcTxId }, options);
}
async save(sourceData: SourceData): Promise<any> {
if (!this.signature) {
throw new Error("Wallet not connected. Use 'connect' method first.");
}
const source = new SourceImpl(this.warp);
const srcTx = await source.save(sourceData, this.warp.environment, this.signature);
return srcTx.id;
}
get rootSortKey(): string {
return this._rootSortKey;
}

View File

@@ -1,5 +1,6 @@
import { JWKInterface } from 'arweave/node/lib/wallet';
import { SignatureType } from '../../contract/Signature';
import { Source } from './Source';
export type Tags = { name: string; value: string }[];
@@ -43,7 +44,7 @@ export interface ContractDeploy {
srcTxId?: string;
}
export interface CreateContract {
export interface CreateContract extends Source {
deploy(contractData: ContractData, disableBundling?: boolean): Promise<ContractDeploy>;
deployFromSourceTx(contractData: FromSrcTxContractData, disableBundling?: boolean): Promise<ContractDeploy>;

View File

@@ -1,17 +1,19 @@
import { ArWallet } from './CreateContract';
import { SourceData } from './impl/SourceImpl';
import { WarpEnvironment } from '../../core/Warp';
import { SignatureType } from '../../contract/Signature';
import Transaction from 'arweave/node/lib/transaction';
export interface Source {
/**
* allows to post contract source on Arweave
* @param contractSource - contract source...
* allows to create contract source
* @param sourceData - contract source data
* @param wallet - either Arweave wallet or custom signature type
*/
save(
contractSource: SourceData,
env: WarpEnvironment,
signer?: ArWallet | SignatureType,
useBundler?: boolean
): Promise<string | null>;
createSourceTx(sourceData: SourceData, wallet: ArWallet | SignatureType): Promise<Transaction>;
/**
* allows to save contract source
* @param sourceTx - contract source transaction
* @param disableBundling = whether source should be deployed through bundlr using Warp Gateway
*/
saveSourceTx(sourceTx: Transaction, disableBundling?: boolean): Promise<string>;
}

View File

@@ -1,21 +1,24 @@
/* eslint-disable */
import Arweave from 'arweave';
import Transaction from 'arweave/node/lib/transaction';
import { Signature } from '../../../contract/Signature';
import { Signature, SignatureType } from '../../../contract/Signature';
import { SmartWeaveTags } from '../../../core/SmartWeaveTags';
import { Warp } from '../../../core/Warp';
import { WARP_GW_URL } from '../../../core/WarpFactory';
import { LoggerFactory } from '../../../logging/LoggerFactory';
import { CreateContract, ContractData, ContractDeploy, FromSrcTxContractData } from '../CreateContract';
import { SourceImpl } from './SourceImpl';
import { CreateContract, ContractData, ContractDeploy, FromSrcTxContractData, ArWallet } from '../CreateContract';
import { SourceData, SourceImpl } from './SourceImpl';
import { Buffer } from 'redstone-isomorphic';
export class DefaultCreateContract implements CreateContract {
private readonly logger = LoggerFactory.INST.create('DefaultCreateContract');
private readonly source: SourceImpl;
private signature: Signature;
constructor(private readonly arweave: Arweave, private warp: Warp) {
this.deployFromSourceTx = this.deployFromSourceTx.bind(this);
this.source = new SourceImpl(this.warp);
}
async deploy(contractData: ContractData, disableBundling?: boolean): Promise<ContractDeploy> {
@@ -24,9 +27,11 @@ export class DefaultCreateContract implements CreateContract {
const effectiveUseBundler =
disableBundling == undefined ? this.warp.definitionLoader.type() == 'warp' : !disableBundling;
const source = new SourceImpl(this.warp);
const srcTx = await this.source.createSourceTx(contractData, wallet);
if (!effectiveUseBundler) {
await this.source.saveSourceTx(srcTx, true);
}
const srcTx = await source.save(contractData, this.warp.environment, wallet, effectiveUseBundler);
this.logger.debug('Creating new contract');
return await this.deployFromSourceTx(
@@ -94,7 +99,7 @@ export class DefaultCreateContract implements CreateContract {
let responseOk: boolean;
let response: { status: number; statusText: string; data: any };
if (effectiveUseBundler) {
const result = await this.post(contractTX, srcTx);
const result = await this.postContract(contractTX, srcTx);
this.logger.debug(result);
responseOk = true;
} else {
@@ -136,7 +141,15 @@ export class DefaultCreateContract implements CreateContract {
}
}
private async post(contractTx: Transaction, srcTx: Transaction = null): Promise<any> {
async createSourceTx(sourceData: SourceData, wallet: ArWallet | SignatureType): Promise<Transaction> {
return this.source.createSourceTx(sourceData, wallet);
}
async saveSourceTx(srcTx: Transaction, disableBundling?: boolean): Promise<string> {
return this.source.saveSourceTx(srcTx, disableBundling);
}
private async postContract(contractTx: Transaction, srcTx: Transaction = null): Promise<any> {
let body: any = {
contractTx
};

View File

@@ -8,8 +8,11 @@ import { SmartWeaveTags } from '../../../core/SmartWeaveTags';
import { LoggerFactory } from '../../../logging/LoggerFactory';
import { Source } from '../Source';
import { Buffer } from 'redstone-isomorphic';
import { Warp, WarpEnvironment } from '../../../core/Warp';
import { Warp } from '../../../core/Warp';
import { Signature, SignatureType } from '../../../contract/Signature';
import Transaction from 'arweave/node/lib/transaction';
import { WARP_GW_URL } from '../../../core/WarpFactory';
import { TagsParser } from '../../../core/modules/impl/TagsParser';
const wasmTypeMapping: Map<number, string> = new Map([
[1, 'assemblyscript'],
@@ -31,21 +34,14 @@ export class SourceImpl implements Source {
constructor(private readonly warp: Warp) {}
async save(
contractData: SourceData,
env: WarpEnvironment,
signature: ArWallet | SignatureType,
useBundler = false
): Promise<any> {
async createSourceTx(sourceData: SourceData, wallet: ArWallet | SignatureType): Promise<Transaction> {
this.logger.debug('Creating new contract source');
const { src, wasmSrcCodeDir, wasmGlueCode } = contractData;
const { src, wasmSrcCodeDir, wasmGlueCode } = sourceData;
this.signature = new Signature(this.warp, signature);
this.signature = new Signature(this.warp, wallet);
const signer = this.signature.signer;
this.signature.checkNonArweaveSigningAvailability(useBundler);
const contractType: ContractType = src instanceof Buffer ? 'wasm' : 'js';
let srcTx;
let wasmLang = null;
@@ -122,7 +118,7 @@ export class SourceImpl implements Source {
srcTx.addTag(SmartWeaveTags.WASM_META, JSON.stringify(metadata));
}
if (env === 'testnet') {
if (this.warp.environment === 'testnet') {
srcTx.addTag(SmartWeaveTags.WARP_TESTNET, '1.0.0');
}
@@ -130,17 +126,40 @@ export class SourceImpl implements Source {
this.logger.debug('Posting transaction with source');
// note: in case of useBundler = true, we're posting both
// src tx and contract tx in one request.
let responseOk = true;
return srcTx;
}
async saveSourceTx(srcTx: Transaction, disableBundling: boolean = false): Promise<string> {
this.logger.debug('Saving contract source', srcTx.id);
if (this.warp.environment == 'local') {
disableBundling = true;
}
const effectiveUseBundler =
disableBundling == undefined ? this.warp.definitionLoader.type() == 'warp' : !disableBundling;
const tagsParser = new TagsParser();
const signatureTag = tagsParser.getTag(srcTx, SmartWeaveTags.SIGNATURE_TYPE);
if (signatureTag && signatureTag != 'arweave' && !effectiveUseBundler) {
throw new Error(`Unable to save source with signature type: ${signatureTag} when bundling is disabled.`);
}
let responseOk: boolean;
let response: { status: number; statusText: string; data: any };
if (!useBundler) {
if (!disableBundling) {
const result = await this.postSource(srcTx);
this.logger.debug(result);
responseOk = true;
} else {
response = await this.warp.arweave.transactions.post(srcTx);
responseOk = response.status === 200 || response.status === 208;
}
if (responseOk) {
return srcTx;
return srcTx.id;
} else {
throw new Error(
`Unable to write Contract Source. Arweave responded with status ${response.status}: ${response.statusText}`
@@ -187,6 +206,26 @@ export class SourceImpl implements Source {
return outputStreamBuffer.getContents();
}
private async postSource(srcTx: Transaction = null): Promise<any> {
const response = await fetch(`${WARP_GW_URL}/gateway/sources/deploy`, {
method: 'POST',
body: JSON.stringify({ srcTx }),
headers: {
'Accept-Encoding': 'gzip, deflate, br',
'Content-Type': 'application/json',
Accept: 'application/json'
}
});
if (response.ok) {
return response.json();
} else {
throw new Error(
`Error while posting contract source. Sequencer responded with status ${response.status} ${response.statusText}`
);
}
}
}
function dummyImports(moduleImports: WebAssembly.ModuleImportDescriptor[]) {

View File

@@ -1,6 +1,12 @@
import Arweave from 'arweave';
import { Contract, InnerCallData } from '../contract/Contract';
import { CreateContract } from '../contract/deploy/CreateContract';
import {
ArWallet,
ContractData,
ContractDeploy,
CreateContract,
FromSrcTxContractData
} from '../contract/deploy/CreateContract';
import { DefaultCreateContract } from '../contract/deploy/impl/DefaultCreateContract';
import { HandlerBasedContract } from '../contract/HandlerBasedContract';
import { PstContract } from '../contract/PstContract';
@@ -15,6 +21,9 @@ import { WarpBuilder } from './WarpBuilder';
import { WarpPluginType, WarpPlugin, knownWarpPlugins } from './WarpPlugin';
import { SortKeyCache } from '../cache/SortKeyCache';
import { ContractDefinition } from './ContractDefinition';
import { SignatureType } from '../contract/Signature';
import { SourceData } from '../contract/deploy/impl/SourceImpl';
import Transaction from 'arweave/node/lib/transaction';
export type WarpEnvironment = 'local' | 'testnet' | 'mainnet' | 'custom';
@@ -27,6 +36,9 @@ export type WarpEnvironment = 'local' | 'testnet' | 'mainnet' | 'custom';
* contract and perform operations on them (see {@link Contract})
*/
export class Warp {
/**
* @deprecated createContract will be a private field, please use its methods directly e.g. await warp.deploy(...)
*/
readonly createContract: CreateContract;
readonly testing: Testing;
@@ -61,6 +73,26 @@ export class Warp {
return new HandlerBasedContract<State>(contractTxId, this, callingContract, innerCallData);
}
async deploy(contractData: ContractData, disableBundling?: boolean): Promise<ContractDeploy> {
return await this.createContract.deploy(contractData, disableBundling);
}
async deployFromSourceTx(contractData: FromSrcTxContractData, disableBundling?: boolean): Promise<ContractDeploy> {
return await this.createContract.deployFromSourceTx(contractData, disableBundling);
}
async deployBundled(rawDataItem: Buffer): Promise<ContractDeploy> {
return await this.createContract.deployBundled(rawDataItem);
}
async createSourceTx(sourceData: SourceData, wallet: ArWallet | SignatureType): Promise<Transaction> {
return await this.createContract.createSourceTx(sourceData, wallet);
}
async saveSourceTx(srcTx: Transaction, disableBundling?: boolean): Promise<string> {
return await this.createContract.saveSourceTx(srcTx, disableBundling);
}
/**
* Allows to connect to a contract that conforms to the Profit Sharing Token standard
* @param contractTxId

View File

@@ -0,0 +1,66 @@
export function handle(state, action) {
const balances = state.balances;
const canEvolve = state.canEvolve;
const input = action.input;
const caller = action.caller;
if (input.function === 'transfer') {
const target = input.target;
const qty = input.qty;
if (!Number.isInteger(qty)) {
throw new ContractError('Invalid value for "qty". Must be an integer');
}
if (!target) {
throw new ContractError('No target specified');
}
if (qty <= 0 || caller === target) {
throw new ContractError('Invalid token transfer');
}
if (balances[caller] < qty) {
throw new ContractError(`Caller balance not high enough to send ${qty} token(s)!`);
}
// Lower the token balance of the caller
balances[caller] -= qty;
if (target in balances) {
// Wallet already exists in state, add new tokens
balances[target] += qty;
} else {
// Wallet is new, set starting balance
balances[target] = qty;
}
return { state };
}
if (input.function === 'balance') {
const target = input.target;
const ticker = state.ticker;
if (typeof target !== 'string') {
throw new ContractError('Must specify target to get balance for');
}
if (typeof balances[target] !== 'number') {
throw new ContractError('Cannot get balance, target does not exist');
}
return { result: { target, ticker, balance: balances[target] + 555 } };
}
if (input.function === 'evolve' && canEvolve) {
if (state.owner !== caller) {
throw new ContractError('Only the owner can evolve a contract.');
}
state.evolve = input.value;
return { state };
}
throw new ContractError(`No function supplied or function not recognised: "${input.function}"`);
}

69
tools/evolve.ts Normal file
View File

@@ -0,0 +1,69 @@
/* eslint-disable */
import { defaultCacheOptions, LoggerFactory, WarpFactory } from '../src';
import fs from 'fs';
import path from 'path';
import { JWKInterface } from 'arweave/node/lib/wallet';
async function main() {
let wallet: JWKInterface = readJSON('./.secrets/jwk.json');
LoggerFactory.INST.logLevel('info');
const logger = LoggerFactory.INST.create('evolve');
try {
const warp = WarpFactory.forMainnet({ ...defaultCacheOptions, inMemory: true });
const jsContractSrc = fs.readFileSync(path.join(__dirname, 'data/js/token-pst.js'), 'utf8');
const newJsContractSrc = fs.readFileSync(path.join(__dirname, 'data/js/token-evolve.js'), 'utf8');
const owner = await warp.arweave.wallets.jwkToAddress(wallet);
const initialState = {
ticker: 'EXAMPLE_PST_TOKEN',
owner,
canEvolve: true,
balances: {
'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M': 10000000,
'33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA': 23111222
},
wallets: {}
};
const { contractTxId } = await warp.deploy({
wallet,
initState: JSON.stringify(initialState),
src: jsContractSrc
});
const contract = warp.contract<any>(contractTxId).connect(wallet);
const { result } = await contract.viewState<any>({
function: 'balance',
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M'
});
console.log('Original result', result);
const srcTx = await warp.createSourceTx({ src: newJsContractSrc }, wallet);
const newSrcTxId = await warp.saveSourceTx(srcTx);
console.log('Save result', newSrcTxId);
await contract.evolve(newSrcTxId);
const { result: evolvedResult } = await contract.viewState<any>({
function: 'balance',
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M'
});
console.log('Evolved result', evolvedResult);
} catch (e) {
logger.error(e);
}
}
export function readJSON(path: string): JWKInterface {
const content = fs.readFileSync(path, 'utf-8');
try {
return JSON.parse(content);
} catch (e) {
throw new Error(`File "${path}" does not contain a valid JSON`);
}
}
main().catch((e) => console.error(e));