feat: deploy contract using evm signature (#257)

* feat: deploy contract using evm signature

* fix: review fixes
This commit is contained in:
Asia
2022-11-24 16:34:02 +01:00
committed by GitHub
parent 0503f473fe
commit cc0791bea0
9 changed files with 244 additions and 78 deletions

View File

@@ -0,0 +1,155 @@
import { Signature } from '../../contract/Signature';
import { defaultCacheOptions, WarpFactory } from '../../core/WarpFactory';
describe('Wallet', () => {
const sampleFunction = async () => {
setTimeout(() => {
//test
}, 1000);
};
const signingFunction = `async (tx) => {await this.warp.arweave.transactions.sign(tx, walletOrSignature);}`.replace(
/\s+/g,
''
);
describe('in local environment', () => {
const warp = WarpFactory.forLocal();
it(`should set correct signature for 'use_wallet'`, () => {
const sut = new Signature(warp, 'use_wallet');
expect(sut.signer.toString().replace(/\s+/g, '')).toEqual(signingFunction);
expect(sut.type).toStrictEqual('arweave');
});
it(`should set correct signature for jwk`, () => {
const sut = new Signature(warp, {
kty: '',
e: '',
n: ''
});
expect(sut.signer.toString().replace(/\s+/g, '')).toEqual(signingFunction);
expect(sut.type).toStrictEqual('arweave');
});
it(`should set correct signature for custom signing function and arweave signature type`, () => {
const sut = new Signature(warp, { signer: sampleFunction, type: 'arweave' });
expect(sut.signer.toString().replace(/\s+/g, '')).toEqual(sampleFunction.toString().replace(/\s+/g, ''));
expect(sut.type).toEqual('arweave');
});
it(`should throw for custom signing function and ethereum signature type`, () => {
expect(() => {
new Signature(warp, { signer: sampleFunction, type: 'ethereum' });
}).toThrow(
`Unable to use signing function of type: ethereum when not in mainnet environment or bundling is disabled.`
);
});
});
describe('in testnet environment', () => {
const warp = WarpFactory.forTestnet();
it(`should set correct signature for 'use_wallet'`, () => {
const sut = new Signature(warp, 'use_wallet');
expect(sut.signer.toString().replace(/\s+/g, '')).toEqual(signingFunction);
expect(sut.type).toStrictEqual('arweave');
});
it(`should set correct signature for jwk`, () => {
const sut = new Signature(warp, {
kty: '',
e: '',
n: ''
});
expect(sut.signer.toString().replace(/\s+/g, '')).toEqual(signingFunction);
expect(sut.type).toStrictEqual('arweave');
});
it(`should set correct signature for custom signing function and arweave signature type`, () => {
const sut = new Signature(warp, { signer: sampleFunction, type: 'arweave' });
expect(sut.signer).toEqual(sampleFunction);
expect(sut.type).toEqual('arweave');
});
it(`should throw for custom signing function and arweave signature type`, () => {
expect(() => {
const sut = new Signature(warp, { signer: sampleFunction, type: 'ethereum' });
}).toThrow(
`Unable to use signing function of type: ethereum when not in mainnet environment or bundling is disabled.`
);
});
});
describe('in mainnet environment when bundling is disabled', () => {
const warp = WarpFactory.forMainnet(defaultCacheOptions, true);
it(`should set correct signature for 'use_wallet'`, () => {
const sut = new Signature(warp, 'use_wallet');
expect(sut.signer.toString().replace(/\s+/g, '')).toEqual(signingFunction);
expect(sut.type).toStrictEqual('arweave');
});
it(`should set correct signature for jwk`, () => {
const sut = new Signature(warp, {
kty: '',
e: '',
n: ''
});
expect(sut.signer.toString().replace(/\s+/g, '')).toEqual(signingFunction);
expect(sut.type).toStrictEqual('arweave');
});
it(`should set correct signature for custom signing function and arweave signature type`, () => {
const sut = new Signature(warp, { signer: sampleFunction, type: 'arweave' });
expect(sut.signer.toString().replace(/\s+/g, '')).toEqual(sampleFunction.toString().replace(/\s+/g, ''));
expect(sut.type).toEqual('arweave');
});
it(`should throw for custom signing function and arweave signature type`, () => {
expect(() => {
const sut = new Signature(warp, { signer: sampleFunction, type: 'ethereum' });
}).toThrow(
`Unable to use signing function of type: ethereum when not in mainnet environment or bundling is disabled.`
);
});
});
describe('in mainnet environment when bundling is enabled', () => {
const warp = WarpFactory.forMainnet();
it(`should set correct signature for 'use_wallet'`, () => {
const sut = new Signature(warp, 'use_wallet');
expect(sut.signer.toString().replace(/\s+/g, '')).toEqual(signingFunction);
expect(sut.type).toStrictEqual('arweave');
});
it(`should set correct signature for jwk`, () => {
const sut = new Signature(warp, {
kty: '',
e: '',
n: ''
});
expect(sut.signer.toString().replace(/\s+/g, '')).toEqual(signingFunction);
expect(sut.type).toStrictEqual('arweave');
});
it(`should set correct signature for custom signing function and arweave signature type`, () => {
const sut = new Signature(warp, { signer: sampleFunction, type: 'arweave' });
expect(sut.signer.toString().replace(/\s+/g, '')).toEqual(sampleFunction.toString().replace(/\s+/g, ''));
expect(sut.type).toEqual('arweave');
});
it(`should set correct signature for custom signing function and ethereum signature type`, () => {
const sut = new Signature(warp, { signer: sampleFunction, type: 'ethereum' });
expect(sut.signer.toString().replace(/\s+/g, '')).toEqual(sampleFunction.toString().replace(/\s+/g, ''));
expect(sut.signer).toEqual(sampleFunction);
});
});
});

View File

@@ -1,4 +1,3 @@
import Transaction from 'arweave/node/lib/transaction';
import { SortKeyCacheResult } from '../cache/SortKeyCache';
import { ContractCallRecord } from '../core/ContractCallRecord';
import { InteractionResult } from '../core/modules/impl/HandlerExecutorFactory';
@@ -6,12 +5,11 @@ import { EvaluationOptions, EvalStateResult } from '../core/modules/StateEvaluat
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 };
export type BenchmarkStats = { gatewayCommunication: number; stateEvaluation: number; total: number };
export type SigningFunction = (tx: Transaction) => Promise<void>;
export type Signature = { signer: SigningFunction; signatureType: 'arweave' | 'ethereum' };
export class ContractError extends Error {
constructor(message) {
super(message);
@@ -85,7 +83,7 @@ export interface Contract<State = unknown> extends Source {
*
* @param signer - either {@link ArWallet} that will be connected to this contract or custom {@link SigningFunction}
*/
connect(signature: ArWallet | Signature): Contract<State>;
connect(signature: ArWallet | SignatureType): Contract<State>;
/**
* Allows to set ({@link EvaluationOptions})

View File

@@ -1,7 +1,6 @@
import { TransactionStatusResponse } from 'arweave/node/transactions';
import stringify from 'safe-stable-stringify';
import * as crypto from 'crypto';
import Transaction from 'arweave/node/lib/transaction';
import { SortKeyCacheResult } from '../cache/SortKeyCache';
import { ContractCallRecord, InteractionCall } from '../core/ContractCallRecord';
import { ExecutionContext } from '../core/ExecutionContext';
@@ -29,13 +28,13 @@ import {
CurrentTx,
WriteInteractionOptions,
WriteInteractionResponse,
InnerCallData,
Signature
InnerCallData
} from './Contract';
import { Tags, ArTransfer, emptyTransfer, ArWallet } from './deploy/CreateContract';
import { SourceData, SourceImpl } from './deploy/impl/SourceImpl';
import { InnerWritesEvaluator } from './InnerWritesEvaluator';
import { generateMockVrf } from '../utils/vrf';
import { Signature, SignatureType } from './Signature';
/**
* An implementation of {@link Contract} that is backwards compatible with current style
@@ -54,11 +53,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
private readonly _arweaveWrapper: ArweaveWrapper;
private _sorter: InteractionsSorter;
private _rootSortKey: string;
/**
* wallet connected to this contract
*/
protected signature?: Signature;
private signature: Signature;
constructor(
private readonly _contractTxId: string,
@@ -229,11 +224,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
const bundleInteraction = interactionsLoader.type() == 'warp' && !effectiveDisableBundling;
if (this.signature.signatureType !== 'arweave' && !bundleInteraction) {
throw new Error(
`Unable to use signing function of type: ${this.signature.signatureType} when not in mainnet environment or bundling is disabled.`
);
}
this.signature.checkNonArweaveSigningAvailability(bundleInteraction);
if (
bundleInteraction &&
@@ -404,29 +395,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
return this._callStack;
}
connect(signature: ArWallet | Signature): Contract<State> {
if (this.isSignatureType(signature)) {
if (
signature.signatureType !== 'arweave' &&
(!(this.warp.environment == 'mainnet') || !(this.warp.interactionsLoader.type() == 'warp'))
) {
throw new Error(
`Unable to use signing function of type: ${signature.signatureType} when not in mainnet environment or bundling is disabled.`
);
} else {
this.signature = {
signer: signature.signer,
signatureType: signature.signatureType
};
}
} else {
this.signature = {
signer: async (tx: Transaction) => {
await this.warp.arweave.transactions.sign(tx, signature);
},
signatureType: 'arweave'
};
}
connect(signature: ArWallet | SignatureType): Contract<State> {
this.signature = new Signature(this.warp, signature);
return this;
}
@@ -788,10 +758,9 @@ export class HandlerBasedContract<State> implements Contract<State> {
if (!this.signature) {
throw new Error("Wallet not connected. Use 'connect' method first.");
}
const { arweave } = this.warp;
const source = new SourceImpl(arweave);
const source = new SourceImpl(this.warp);
const srcTx = await source.save(sourceData, this.warp.environment, this.signature.signer);
const srcTx = await source.save(sourceData, this.warp.environment, this.signature);
return srcTx.id;
}
@@ -799,8 +768,4 @@ export class HandlerBasedContract<State> implements Contract<State> {
get rootSortKey(): string {
return this._rootSortKey;
}
private isSignatureType(signature: ArWallet | Signature): signature is Signature {
return (signature as Signature).signer !== undefined;
}
}

45
src/contract/Signature.ts Normal file
View File

@@ -0,0 +1,45 @@
import Transaction from 'arweave/node/lib/transaction';
import { Warp } from 'core/Warp';
import { ArWallet } from './deploy/CreateContract';
export type SigningFunction = (tx: Transaction) => Promise<void>;
export type SignatureType = { signer: SigningFunction; type: 'arweave' | 'ethereum' };
export class Signature {
readonly signer: SigningFunction;
readonly type: 'arweave' | 'ethereum';
readonly warp: Warp;
constructor(warp: Warp, walletOrSignature: ArWallet | SignatureType) {
this.warp = warp;
if (this.isSignatureType(walletOrSignature)) {
if (
walletOrSignature.type !== 'arweave' &&
(!(this.warp.environment == 'mainnet') || !(this.warp.interactionsLoader.type() == 'warp'))
) {
throw new Error(
`Unable to use signing function of type: ${walletOrSignature.type} when not in mainnet environment or bundling is disabled.`
);
} else {
this.signer = walletOrSignature.signer;
this.type = walletOrSignature.type;
}
} else {
this.signer = async (tx: Transaction) => {
await this.warp.arweave.transactions.sign(tx, walletOrSignature);
};
this.type = 'arweave';
}
}
checkNonArweaveSigningAvailability(bundling: boolean): void {
if (this.type !== 'arweave' && !bundling) {
throw new Error(`Unable to use signing function of type: ${this.type} when bundling is disabled.`);
}
}
private isSignatureType(signature: ArWallet | SignatureType): signature is SignatureType {
return (signature as SignatureType).signer !== undefined;
}
}

View File

@@ -1,4 +1,5 @@
import { JWKInterface } from 'arweave/node/lib/wallet';
import { SignatureType } from '../../contract/Signature';
export type Tags = { name: string; value: string }[];
@@ -17,7 +18,7 @@ export const emptyTransfer: ArTransfer = {
};
export interface CommonContractData {
wallet: ArWallet;
wallet: ArWallet | SignatureType;
initState: string;
tags?: Tags;
transfer?: ArTransfer;

View File

@@ -1,7 +1,7 @@
import { SigningFunction } from '../../contract/Contract';
import { ArWallet } from './CreateContract';
import { SourceData } from './impl/SourceImpl';
import { WarpEnvironment } from '../../core/Warp';
import { SignatureType } from '../../contract/Signature';
export interface Source {
/**
@@ -11,7 +11,7 @@ export interface Source {
save(
contractSource: SourceData,
env: WarpEnvironment,
signer?: ArWallet | SigningFunction,
signer?: ArWallet | SignatureType,
useBundler?: boolean
): Promise<string | null>;
}

View File

@@ -1,6 +1,7 @@
/* eslint-disable */
import Arweave from 'arweave';
import Transaction from 'arweave/node/lib/transaction';
import { Signature } from '../../../contract/Signature';
import { SmartWeaveTags } from '../../../core/SmartWeaveTags';
import { Warp } from '../../../core/Warp';
import { WARP_GW_URL } from '../../../core/WarpFactory';
@@ -11,6 +12,7 @@ import { Buffer } from 'redstone-isomorphic';
export class DefaultCreateContract implements CreateContract {
private readonly logger = LoggerFactory.INST.create('DefaultCreateContract');
private signature: Signature;
constructor(private readonly arweave: Arweave, private warp: Warp) {
this.deployFromSourceTx = this.deployFromSourceTx.bind(this);
@@ -22,7 +24,7 @@ export class DefaultCreateContract implements CreateContract {
const effectiveUseBundler =
disableBundling == undefined ? this.warp.definitionLoader.type() == 'warp' : !disableBundling;
const source = new SourceImpl(this.arweave);
const source = new SourceImpl(this.warp);
const srcTx = await source.save(contractData, this.warp.environment, wallet, effectiveUseBundler);
this.logger.debug('Creating new contract');
@@ -48,22 +50,23 @@ export class DefaultCreateContract implements CreateContract {
): Promise<ContractDeploy> {
this.logger.debug('Creating new contract from src tx');
const { wallet, srcTxId, initState, tags, transfer, data } = contractData;
this.signature = new Signature(this.warp, wallet);
const signer = this.signature.signer;
const effectiveUseBundler =
disableBundling == undefined ? this.warp.definitionLoader.type() == 'warp' : !disableBundling;
let contractTX = await this.arweave.createTransaction({ data: data?.body || initState }, wallet);
this.signature.checkNonArweaveSigningAvailability(effectiveUseBundler);
let contractTX = await this.arweave.createTransaction({ data: data?.body || initState });
if (+transfer?.winstonQty > 0 && transfer.target.length) {
this.logger.debug('Creating additional transaction with AR transfer', transfer);
contractTX = await this.arweave.createTransaction(
{
data: data?.body || initState,
target: transfer.target,
quantity: transfer.winstonQty
},
wallet
);
contractTX = await this.arweave.createTransaction({
data: data?.body || initState,
target: transfer.target,
quantity: transfer.winstonQty
});
}
if (tags?.length) {
@@ -86,7 +89,7 @@ export class DefaultCreateContract implements CreateContract {
contractTX.addTag(SmartWeaveTags.WARP_TESTNET, '1.0.0');
}
await this.arweave.transactions.sign(contractTX, wallet);
await signer(contractTX);
let responseOk: boolean;
let response: { status: number; statusText: string; data: any };

View File

@@ -1,16 +1,15 @@
/* eslint-disable */
import metering from 'redstone-wasm-metering';
import Arweave from 'arweave';
import { Go } from '../../../core/modules/impl/wasm/go-wasm-imports';
import fs, { PathOrFileDescriptor } from 'fs';
import { matchMutClosureDtor } from '../../../core/modules/impl/wasm/wasm-bindgen-tools';
import { ArWallet, ContractType } from '../CreateContract';
import { SigningFunction } from '../../../contract/Contract';
import { SmartWeaveTags } from '../../../core/SmartWeaveTags';
import { LoggerFactory } from '../../../logging/LoggerFactory';
import { Source } from '../Source';
import { Buffer } from 'redstone-isomorphic';
import { WarpEnvironment } from '../../../core/Warp';
import { Warp, WarpEnvironment } from '../../../core/Warp';
import { Signature, SignatureType } from '../../../contract/Signature';
const wasmTypeMapping: Map<number, string> = new Map([
[1, 'assemblyscript'],
@@ -28,18 +27,25 @@ export interface SourceData {
export class SourceImpl implements Source {
private readonly logger = LoggerFactory.INST.create('Source');
constructor(private readonly arweave: Arweave) {}
private signature: Signature;
constructor(private readonly warp: Warp) {}
async save(
contractData: SourceData,
env: WarpEnvironment,
signer: ArWallet | SigningFunction,
signature: ArWallet | SignatureType,
useBundler = false
): Promise<any> {
this.logger.debug('Creating new contract source');
const { src, wasmSrcCodeDir, wasmGlueCode } = contractData;
this.signature = new Signature(this.warp, signature);
const signer = this.signature.signer;
this.signature.checkNonArweaveSigningAvailability(useBundler);
const contractType: ContractType = src instanceof Buffer ? 'wasm' : 'js';
let srcTx;
let wasmLang = null;
@@ -102,11 +108,7 @@ export class SourceImpl implements Source {
const allData = contractType == 'wasm' ? this.joinBuffers(data) : src;
if (typeof signer == 'function') {
srcTx = await this.arweave.createTransaction({ data: allData });
} else {
srcTx = await this.arweave.createTransaction({ data: allData }, signer);
}
srcTx = await this.warp.arweave.createTransaction({ data: allData });
srcTx.addTag(SmartWeaveTags.APP_NAME, 'SmartWeaveContractSource');
// TODO: version should be taken from the current package.json version.
@@ -124,11 +126,8 @@ export class SourceImpl implements Source {
srcTx.addTag(SmartWeaveTags.WARP_TESTNET, '1.0.0');
}
if (typeof signer == 'function') {
await signer(srcTx);
} else {
await this.arweave.transactions.sign(srcTx, signer);
}
await signer(srcTx);
this.logger.debug('Posting transaction with source');
// note: in case of useBundler = true, we're posting both
@@ -136,7 +135,7 @@ export class SourceImpl implements Source {
let responseOk = true;
let response: { status: number; statusText: string; data: any };
if (!useBundler) {
response = await this.arweave.transactions.post(srcTx);
response = await this.warp.arweave.transactions.post(srcTx);
responseOk = response.status === 200 || response.status === 208;
}

View File

@@ -4,8 +4,8 @@ import { CreateTransactionInterface } from 'arweave/node/common';
import { BlockData } from 'arweave/node/blocks';
import { SmartWeaveTags } from '../core/SmartWeaveTags';
import { GQLNodeInterface } from './gqlResult';
import { SigningFunction } from '../contract/Contract';
import { TagsParser } from '../core/modules/impl/TagsParser';
import { SigningFunction } from '../contract/Signature';
export async function createInteractionTx(
arweave: Arweave,