feat: protection against read after making a internal write
This commit is contained in:
@@ -59,7 +59,7 @@ Transaction which is sent to Bundlr, consists of:
|
||||
**NOTE** The original transaction is not modified in any way - this is to preserve the original
|
||||
signature!
|
||||
|
||||
2. After receiving proper response and recipes from Bundlr, the Warp gateway indexes the contract
|
||||
2. After receiving proper response and receipt from Bundlr, the Warp gateway indexes the contract
|
||||
transactions data internally - to make them instantly available.
|
||||
|
||||
3. Finally, the Warp gateway returns an object as a `response` - that consists of fields:
|
||||
|
||||
@@ -130,7 +130,7 @@ Using the `sort_key`, `vrf-proof` and `vrf-pubkey`, the client can always verify
|
||||
**NOTE** The original transaction is not modified in any way - this is to preserve the original
|
||||
signature!
|
||||
|
||||
After receiving proper response and recipes from Bundlr, the Warp gateway indexes the contract interaction
|
||||
After receiving proper response and receipt from Bundlr, the Warp gateway indexes the contract interaction
|
||||
internally - to make it instantly available.
|
||||
|
||||
#### 4. Finally, the Warp gateway returns the response from the Bundlr to the client.
|
||||
|
||||
@@ -74,17 +74,13 @@ async function handle(state, action) {
|
||||
if (input.lockLength) {
|
||||
lockLength = input.lockLength;
|
||||
}
|
||||
console.log('====== AFTR CONTRACT deposit FN - calling claim on: ', input.tokenId);
|
||||
const transferResult = await SmartWeave.contracts.write(input.tokenId, {
|
||||
function: "claim",
|
||||
txID: input.txID,
|
||||
qty: input.qty
|
||||
});
|
||||
console.log('====== AFTR CONTRACT deposit FN - calling claim result ', transferResult);
|
||||
console.log('====== AFTR CONTRACT deposit FN - PST state', transferResult.state);
|
||||
|
||||
const tokenInfo = getTokenInfo(transferResult.state);
|
||||
console.log('Token info', tokenInfo);
|
||||
const txObj = {
|
||||
txID: input.txID,
|
||||
tokenId: input.tokenId,
|
||||
@@ -128,7 +124,6 @@ async function handle(state, action) {
|
||||
});
|
||||
}
|
||||
if (input.function === "claim") {
|
||||
console.log('====== Claim function BEGIN ===');
|
||||
const txID = input.txID;
|
||||
const qty = input.qty;
|
||||
if (!state.claimable.length) {
|
||||
@@ -161,8 +156,6 @@ async function handle(state, action) {
|
||||
balances[caller] += obj.qty;
|
||||
state.claimable.splice(index, 1);
|
||||
state.claims.push(txID);
|
||||
|
||||
console.log('====== Claim function END ===');
|
||||
}
|
||||
|
||||
|
||||
|
||||
196
src/__tests__/integration/data/aftr/sampleContractSrc_broken.js
Normal file
196
src/__tests__/integration/data/aftr/sampleContractSrc_broken.js
Normal file
@@ -0,0 +1,196 @@
|
||||
async function handle(state, action) {
|
||||
const balances = state.balances;
|
||||
const input = action.input;
|
||||
const caller = action.caller;
|
||||
let target = "";
|
||||
let balance = 0;
|
||||
|
||||
if (input.function === "balance") {
|
||||
target = isArweaveAddress(input.target || caller);
|
||||
if (typeof target !== "string") {
|
||||
throw new ContractError("Must specificy target to get balance for.");
|
||||
}
|
||||
balance = 0;
|
||||
if (target in balances) {
|
||||
balance = balances[target];
|
||||
}
|
||||
}
|
||||
|
||||
if (input.function === "transfer") {
|
||||
const target2 = input.target;
|
||||
const qty = input.qty;
|
||||
const callerAddress = isArweaveAddress(caller);
|
||||
const targetAddress = isArweaveAddress(target2);
|
||||
if (!Number.isInteger(qty)) {
|
||||
throw new ContractError('Invalid value for "qty". Must be an integer.');
|
||||
}
|
||||
if (!targetAddress) {
|
||||
throw new ContractError("No target specified.");
|
||||
}
|
||||
if (qty <= 0 || callerAddress === targetAddress) {
|
||||
throw new ContractError("Invalid token transfer.");
|
||||
}
|
||||
if (!(callerAddress in balances)) {
|
||||
throw new ContractError("Caller doesn't own a balance in the Vehicle.");
|
||||
}
|
||||
if (balances[callerAddress] < qty) {
|
||||
throw new ContractError(`Caller balance not high enough to send ${qty} token(s)!`);
|
||||
}
|
||||
if (SmartWeave.contract.id === target2) {
|
||||
throw new ContractError("A vehicle token cannot be transferred to itself because it would add itself the balances object of the vehicle, thus changing the membership of the vehicle without a vote.");
|
||||
}
|
||||
if (state.ownership === "single" && callerAddress === state.creator && balances[callerAddress] - qty <= 0) {
|
||||
throw new ContractError("Invalid transfer because the creator's balance would be 0.");
|
||||
}
|
||||
balances[callerAddress] -= qty;
|
||||
if (targetAddress in balances) {
|
||||
balances[targetAddress] += qty;
|
||||
} else {
|
||||
balances[targetAddress] = qty;
|
||||
}
|
||||
}
|
||||
if (input.function === "mint") {
|
||||
if (!input.qty) {
|
||||
throw new ContractError("Missing qty.");
|
||||
}
|
||||
if (!(caller in state.balances)) {
|
||||
balances[caller] = input.qty;
|
||||
}
|
||||
}
|
||||
if (input.function === "deposit") {
|
||||
if (!input.txID) {
|
||||
throw new ContractError("The transaction is not valid. Tokens were not transferred to the vehicle.");
|
||||
}
|
||||
if (!input.tokenId) {
|
||||
throw new ContractError("No token supplied. Tokens were not transferred to the vehicle.");
|
||||
}
|
||||
if (input.tokenId === SmartWeave.contract.id) {
|
||||
throw new ContractError("Deposit not allowed because you can't deposit an asset of itself.");
|
||||
}
|
||||
if (!input.qty || typeof +input.qty !== "number" || +input.qty <= 0) {
|
||||
throw new ContractError("Qty is invalid.");
|
||||
}
|
||||
let lockLength = 0;
|
||||
if (input.lockLength) {
|
||||
lockLength = input.lockLength;
|
||||
}
|
||||
|
||||
await SmartWeave.contracts.write(input.tokenId, {
|
||||
function: "claim",
|
||||
txID: input.txID,
|
||||
qty: input.qty
|
||||
});
|
||||
|
||||
// note: getTokenInfo underneath makes a readContractState on input.tokenId
|
||||
// the SDK should now throw in such case (i.e. making a read on a contract on which
|
||||
// we've just made write).
|
||||
const tokenInfo = await getTokenInfo(input.tokenId);
|
||||
const txObj = {
|
||||
txID: input.txID,
|
||||
tokenId: input.tokenId,
|
||||
source: caller,
|
||||
balance: input.qty,
|
||||
start: SmartWeave.block.height,
|
||||
name: tokenInfo.name,
|
||||
ticker: tokenInfo.ticker,
|
||||
logo: tokenInfo.logo,
|
||||
lockLength
|
||||
};
|
||||
if (!state.tokens) {
|
||||
state["tokens"] = [];
|
||||
}
|
||||
state.tokens.push(txObj);
|
||||
}
|
||||
if (input.function === "allow") {
|
||||
target = input.target;
|
||||
const quantity = input.qty;
|
||||
if (!Number.isInteger(quantity) || quantity === void 0) {
|
||||
throw new ContractError("Invalid value for quantity. Must be an integer.");
|
||||
}
|
||||
if (!target) {
|
||||
throw new ContractError("No target specified.");
|
||||
}
|
||||
if (quantity <= 0 || caller === target) {
|
||||
throw new ContractError("Invalid token transfer.");
|
||||
}
|
||||
if (balances[caller] < quantity) {
|
||||
throw new ContractError("Caller balance not high enough to make claimable " + quantity + " token(s).");
|
||||
}
|
||||
balances[caller] -= quantity;
|
||||
if (balances[caller] === null || balances[caller] === void 0) {
|
||||
balances[caller] = 0;
|
||||
}
|
||||
state.claimable.push({
|
||||
from: caller,
|
||||
to: target,
|
||||
qty: quantity,
|
||||
txID: SmartWeave.transaction.id
|
||||
});
|
||||
}
|
||||
if (input.function === "claim") {
|
||||
const txID = input.txID;
|
||||
const qty = input.qty;
|
||||
if (!state.claimable.length) {
|
||||
throw new ContractError("Contract has no claims available.");
|
||||
}
|
||||
let obj, index;
|
||||
for (let i = 0; i < state.claimable.length; i++) {
|
||||
if (state.claimable[i].txID === txID) {
|
||||
index = i;
|
||||
obj = state.claimable[i];
|
||||
}
|
||||
}
|
||||
if (obj === void 0) {
|
||||
throw new ContractError("Unable to find claim.");
|
||||
}
|
||||
if (obj.to !== caller) {
|
||||
throw new ContractError("Claim not addressed to caller.");
|
||||
}
|
||||
if (obj.qty !== qty) {
|
||||
throw new ContractError("Claiming incorrect quantity of tokens.");
|
||||
}
|
||||
for (let i = 0; i < state.claims.length; i++) {
|
||||
if (state.claims[i] === txID) {
|
||||
throw new ContractError("This claim has already been made.");
|
||||
}
|
||||
}
|
||||
if (!balances[caller]) {
|
||||
balances[caller] = 0;
|
||||
}
|
||||
balances[caller] += obj.qty;
|
||||
state.claimable.splice(index, 1);
|
||||
state.claims.push(txID);
|
||||
}
|
||||
|
||||
|
||||
if (input.function === "balance") {
|
||||
let vaultBal = 0;
|
||||
try {
|
||||
for (let bal of state.vault[caller]) {
|
||||
vaultBal += bal.balance;
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
return {result: {target, balance, vaultBal}};
|
||||
} else {
|
||||
return {state};
|
||||
}
|
||||
}
|
||||
|
||||
function isArweaveAddress(addy) {
|
||||
const address = addy.toString().trim();
|
||||
if (!/[a-z0-9_-]{43}/i.test(address)) {
|
||||
throw new ContractError("Invalid Arweave address.");
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
async function getTokenInfo(contractId) {
|
||||
const assetState = await SmartWeave.contracts.readContractState(contractId);
|
||||
const settings = new Map(assetState.settings);
|
||||
return {
|
||||
name: currentTokenState.name,
|
||||
ticker: currentTokenState.ticker,
|
||||
logo: settings.get("communityLogo")
|
||||
};
|
||||
}
|
||||
@@ -49,7 +49,6 @@ export async function handle(state, action) {
|
||||
return { result: value };
|
||||
}
|
||||
if (action.input.function === 'justThrow') {
|
||||
console.log('called justThrow');
|
||||
throw new ContractError('Error from justThrow function');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ export async function handle(state, action) {
|
||||
}
|
||||
|
||||
if (action.input.function === 'writeContractAutoThrow') {
|
||||
console.log('before calling justThrow');
|
||||
await SmartWeave.contracts.write(action.input.contractId, {
|
||||
function: 'justThrow',
|
||||
});
|
||||
@@ -17,7 +16,6 @@ export async function handle(state, action) {
|
||||
state.errorCounter = 0;
|
||||
}
|
||||
state.errorCounter++;
|
||||
console.log('after calling justThrow', state.errorCounter);
|
||||
return { state };
|
||||
}
|
||||
if (action.input.function === 'writeContractForceAutoThrow') {
|
||||
|
||||
@@ -281,7 +281,74 @@ describe('Testing internal writes', () => {
|
||||
['communityLogo', '']
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('AFTR test case - with an illegal read state after an internal write', () => {
|
||||
it('should throw an Error if contract makes readContractState after write on the same contract', async () => {
|
||||
const newWarpInstance = WarpFactory.forLocal(port);
|
||||
const { jwk: wallet2 } = await newWarpInstance.testing.generateWallet();
|
||||
|
||||
const aftrBrokenContractSrc = fs.readFileSync(path.join(__dirname, '../data/aftr/sampleContractSrc_broken.js'), 'utf8');
|
||||
|
||||
const aftrBrokenContractInitialState = fs.readFileSync(path.join(__dirname, '../data/aftr/sampleContractInitState.json'), 'utf8');
|
||||
const pst2InitState = fs.readFileSync(path.join(__dirname, '../data/aftr/pstInitState.json'), 'utf8');
|
||||
|
||||
const {contractTxId: aftrBrokenTxId, srcTxId: brokenSrcTxId} = await newWarpInstance.createContract.deploy({
|
||||
wallet: wallet2,
|
||||
initState: aftrBrokenContractInitialState,
|
||||
src: aftrBrokenContractSrc
|
||||
});
|
||||
|
||||
const {contractTxId: pst2TxId} = await newWarpInstance.createContract.deployFromSourceTx({
|
||||
wallet: wallet2,
|
||||
initState: pst2InitState,
|
||||
srcTxId: brokenSrcTxId
|
||||
});
|
||||
|
||||
const aftrBroken = newWarpInstance
|
||||
.contract<any>(aftrBrokenTxId)
|
||||
.setEvaluationOptions({
|
||||
internalWrites: true
|
||||
})
|
||||
.connect(wallet2);
|
||||
|
||||
const pst2 = newWarpInstance
|
||||
.contract<any>(pst2TxId)
|
||||
.setEvaluationOptions({
|
||||
internalWrites: true
|
||||
})
|
||||
.connect(wallet2);
|
||||
|
||||
await mineBlock(newWarpInstance);
|
||||
|
||||
// (o) mint 10000 tokens in pst contract
|
||||
await pst2.writeInteraction({
|
||||
function: "mint",
|
||||
qty: 10000
|
||||
});
|
||||
|
||||
const transferQty = 1;
|
||||
// (o) set allowance on pst contract for aftr contract
|
||||
const {originalTxId} = await pst2.writeInteraction({
|
||||
function: "allow",
|
||||
target: aftrBrokenTxId,
|
||||
qty: transferQty,
|
||||
});
|
||||
|
||||
// (o) make a deposit transaction on the AFTR contract
|
||||
// note: this transaction makes internalWrite on PST contract
|
||||
// and then makes a readContractState on a PST contract
|
||||
// - such operation is not allowed.
|
||||
await expect(aftrBroken.writeInteraction({
|
||||
function: "deposit",
|
||||
tokenId: pst2TxId,
|
||||
qty: transferQty,
|
||||
txID: originalTxId,
|
||||
}, { strict: true })).rejects.toThrowError(
|
||||
/Calling a readContractState after performing an inner write is wrong - instead use a state from the result of an internal write./
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -279,7 +279,7 @@ describe('Testing internal writes', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('with internal writes throwing exceptions', () => {
|
||||
fdescribe('with internal writes throwing exceptions', () => {
|
||||
beforeAll(async () => {
|
||||
await deployContracts();
|
||||
});
|
||||
|
||||
@@ -63,6 +63,10 @@ export interface EvolveState {
|
||||
evolve: string;
|
||||
}
|
||||
|
||||
export type InnerCallType = 'read' | 'view' | 'write';
|
||||
|
||||
export type InnerCallData = { callingInteraction: GQLNodeInterface; callType: InnerCallType };
|
||||
|
||||
/**
|
||||
* A base interface to be implemented by SmartWeave Contracts clients
|
||||
* - contains "low-level" methods that allow to interact with any contract
|
||||
@@ -91,8 +95,7 @@ export interface Contract<State = unknown> extends Source {
|
||||
setEvaluationOptions(options: Partial<EvaluationOptions>): Contract<State>;
|
||||
|
||||
/**
|
||||
* Returns state of the contract at required blockHeight.
|
||||
* Similar to {@link readContract} from the current version.
|
||||
* Returns state of the contract at required sortKey or blockHeight.
|
||||
*
|
||||
* @param sortKeyOrBlockHeight - either a sortKey or block height at which the contract should be read
|
||||
*
|
||||
|
||||
@@ -29,7 +29,8 @@ import {
|
||||
SigningFunction,
|
||||
CurrentTx,
|
||||
WriteInteractionOptions,
|
||||
WriteInteractionResponse
|
||||
WriteInteractionResponse,
|
||||
InnerCallData
|
||||
} from './Contract';
|
||||
import { Tags, ArTransfer, emptyTransfer, ArWallet } from './deploy/CreateContract';
|
||||
import { SourceData, SourceImpl } from './deploy/impl/SourceImpl';
|
||||
@@ -62,7 +63,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
private readonly _contractTxId: string,
|
||||
protected readonly warp: Warp,
|
||||
private readonly _parentContract: Contract = null,
|
||||
private readonly _callingInteraction: GQLNodeInterface = null
|
||||
private readonly _innerCallData: InnerCallData = null
|
||||
) {
|
||||
this.waitForConfirmation = this.waitForConfirmation.bind(this);
|
||||
this._arweaveWrapper = new ArweaveWrapper(warp.arweave);
|
||||
@@ -70,7 +71,9 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
if (_parentContract != null) {
|
||||
this._evaluationOptions = _parentContract.evaluationOptions();
|
||||
this._callDepth = _parentContract.callDepth() + 1;
|
||||
const callingInteraction: InteractionCall = _parentContract.getCallStack().getInteraction(_callingInteraction.id);
|
||||
const callingInteraction: InteractionCall = _parentContract
|
||||
.getCallStack()
|
||||
.getInteraction(_innerCallData.callingInteraction.id);
|
||||
|
||||
if (this._callDepth > this._evaluationOptions.maxCallDepth) {
|
||||
throw Error(
|
||||
@@ -79,14 +82,30 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
)}`
|
||||
);
|
||||
}
|
||||
this.logger.debug('Calling interaction', { id: _callingInteraction.id, sortKey: _callingInteraction.sortKey });
|
||||
const callStack = new ContractCallStack(_contractTxId, this._callDepth);
|
||||
this.logger.debug('Calling interaction', {
|
||||
id: _innerCallData.callingInteraction.id,
|
||||
sortKey: _innerCallData.callingInteraction.sortKey,
|
||||
type: _innerCallData.callType
|
||||
});
|
||||
|
||||
// if you're reading a state of the contract, on which you've just made a write - you're doing it wrong.
|
||||
// the current state of the callee contract is always in the result of an internal write.
|
||||
// following is a protection against naughty developers who might be doing such crazy things ;-)
|
||||
if (
|
||||
callingInteraction.interactionInput?.foreignContractCalls[_contractTxId]?.innerCallType === 'write' &&
|
||||
_innerCallData.callType === 'read'
|
||||
) {
|
||||
throw new Error(
|
||||
'Calling a readContractState after performing an inner write is wrong - instead use a state from the result of an internal write.'
|
||||
);
|
||||
}
|
||||
|
||||
const callStack = new ContractCallStack(_contractTxId, this._callDepth, _innerCallData?.callType);
|
||||
callingInteraction.interactionInput.foreignContractCalls[_contractTxId] = callStack;
|
||||
this._callStack = callStack;
|
||||
this._rootSortKey = _parentContract.rootSortKey;
|
||||
|
||||
console.log('====CHILD constructor, parent callstack: ', _parentContract.getCallStack().print());
|
||||
|
||||
// console.log('==== CHILD constructor, parent callstack: ', _parentContract.getCallStack().print());
|
||||
} else {
|
||||
this._callDepth = 0;
|
||||
this._callStack = new ContractCallStack(_contractTxId, 0);
|
||||
@@ -318,7 +337,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
const handlerResult = await this.callContract(input, undefined, undefined, tags, transfer, strict);
|
||||
|
||||
if ((input as any).function === 'deposit') {
|
||||
console.log('====== handlerResult ======', handlerResult);
|
||||
// console.log('====== handlerResult ======', handlerResult);
|
||||
}
|
||||
if (strict && handlerResult.type !== 'ok') {
|
||||
throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`);
|
||||
@@ -329,8 +348,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
this.logger.debug('Callstack', callStack.print());
|
||||
|
||||
if ((input as any).function === 'deposit') {
|
||||
console.log('====== Call stack ======', callStack.print());
|
||||
console.log('====== Inner Writes ======', innerWrites);
|
||||
// console.log('====== Call stack ======', callStack.print());
|
||||
// console.log('====== Inner Writes ======', innerWrites);
|
||||
}
|
||||
|
||||
innerWrites.forEach((contractTxId) => {
|
||||
@@ -624,13 +643,13 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
// there could haven been some earlier, non-cached interactions - which will be added
|
||||
// after eval in line 619. We need to clear them, as it is only the currently
|
||||
// being added interaction that we're interested in.
|
||||
if (this._parentContract) {
|
||||
/*if (this._parentContract) {
|
||||
console.log('======== CLEARING CALL STACK');
|
||||
const callStack = new ContractCallStack(this.txId(), this._callDepth);
|
||||
const callingInteraction = this._parentContract.getCallStack().getInteraction(this._callingInteraction.id);
|
||||
callingInteraction.interactionInput.foreignContractCalls[this.txId()] = callStack;
|
||||
this._callStack = callStack;
|
||||
}
|
||||
}*/
|
||||
this.logger.debug('callContractForTx - evalStateResult', {
|
||||
result: evalStateResult.cachedValue.state,
|
||||
txId: this._contractTxId
|
||||
@@ -647,7 +666,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
currentTx
|
||||
};
|
||||
|
||||
console.log('====== evalInteraction');
|
||||
// console.log('====== evalInteraction');
|
||||
const result = await this.evalInteraction<Input, View>(
|
||||
interactionData,
|
||||
executionContext,
|
||||
@@ -664,15 +683,15 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
executionContext: ExecutionContext<State, HandlerApi<State>>,
|
||||
evalStateResult: EvalStateResult<State>
|
||||
) {
|
||||
console.log('====== addInteractionData to callStack', interactionData.interaction.input);
|
||||
console.log('====== addInteractionData to callStack - callstack', this.getCallStack().print());
|
||||
console.log('====== addInteractionData to callStack - parent callstack', this.parent()?.getCallStack().print());
|
||||
// console.log('====== addInteractionData to callStack', interactionData.interaction.input);
|
||||
// console.log('====== addInteractionData to callStack - callstack', this.getCallStack().print());
|
||||
// console.log('====== addInteractionData to callStack - parent callstack', this.parent()?.getCallStack().print());
|
||||
|
||||
const interactionCall: InteractionCall = this.getCallStack().addInteractionData(interactionData);
|
||||
|
||||
console.log('====== AFTER ADDING ======');
|
||||
console.log('====== addInteractionData to callStack - callstack', this.getCallStack().print());
|
||||
console.log('====== addInteractionData to callStack - parent callstack', this.parent()?.getCallStack().print());
|
||||
// console.log('====== AFTER ADDING ======');
|
||||
// console.log('====== addInteractionData to callStack - callstack', this.getCallStack().print());
|
||||
// console.log('====== addInteractionData to callStack - parent callstack', this.parent()?.getCallStack().print());
|
||||
|
||||
const benchmark = Benchmark.measure();
|
||||
const result = await executionContext.handler.handle<Input, View>(
|
||||
@@ -681,6 +700,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
interactionData
|
||||
);
|
||||
|
||||
// console.log('RESULT', result);
|
||||
|
||||
interactionCall.update({
|
||||
cacheHit: false,
|
||||
outputState: this._evaluationOptions.stackTrace.saveState ? result.state : undefined,
|
||||
@@ -690,8 +711,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
gasUsed: result.gasUsed
|
||||
});
|
||||
|
||||
console.log('==== Callstack after interaction call', this.getCallStack().print());
|
||||
console.log('==== PARENT Callstack after interaction call', this.parent()?.getCallStack().print());
|
||||
// console.log('==== Callstack after interaction call', this.getCallStack().print());
|
||||
// console.log('==== PARENT Callstack after interaction call', this.parent()?.getCallStack().print());
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -763,10 +784,6 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
return srcTx.id;
|
||||
}
|
||||
|
||||
get callingInteraction(): GQLNodeInterface | null {
|
||||
return this._callingInteraction;
|
||||
}
|
||||
|
||||
get rootSortKey(): string {
|
||||
return this._rootSortKey;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { InteractionData } from './modules/impl/HandlerExecutorFactory';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { InnerCallType } from '../contract/Contract';
|
||||
|
||||
export class ContractCallStack {
|
||||
readonly interactions: { [key: string]: InteractionCall } = {};
|
||||
readonly id: string;
|
||||
|
||||
constructor(
|
||||
public readonly contractTxId: string,
|
||||
public readonly depth: number,
|
||||
public readonly label: string = '',
|
||||
public readonly id = randomUUID()
|
||||
) {}
|
||||
constructor(readonly contractTxId: string, readonly depth: number, readonly innerCallType: InnerCallType = null) {
|
||||
this.id = randomUUID();
|
||||
}
|
||||
|
||||
addInteractionData(interactionData: InteractionData<any>): InteractionCall {
|
||||
const { interaction, interactionTx } = interactionData;
|
||||
@@ -38,7 +37,7 @@ export class ContractCallStack {
|
||||
}
|
||||
|
||||
print(): string {
|
||||
return JSON.stringify(this);
|
||||
return JSON.stringify(this, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Arweave from 'arweave';
|
||||
import { LevelDbCache } from '../cache/impl/LevelDbCache';
|
||||
import { Contract } from '../contract/Contract';
|
||||
import {Contract, InnerCallData, InnerCallType} from '../contract/Contract';
|
||||
import { CreateContract } from '../contract/deploy/CreateContract';
|
||||
import { DefaultCreateContract } from '../contract/deploy/impl/DefaultCreateContract';
|
||||
import { HandlerBasedContract } from '../contract/HandlerBasedContract';
|
||||
@@ -61,9 +61,9 @@ export class Warp {
|
||||
contract<State>(
|
||||
contractTxId: string,
|
||||
callingContract?: Contract,
|
||||
callingInteraction?: GQLNodeInterface
|
||||
innerCallData?: InnerCallData
|
||||
): Contract<State> {
|
||||
return new HandlerBasedContract<State>(contractTxId, this, callingContract, callingInteraction);
|
||||
return new HandlerBasedContract<State>(contractTxId, this, callingContract, innerCallData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -107,11 +107,10 @@ export abstract class DefaultStateEvaluator implements StateEvaluator {
|
||||
.addInteractionData({ interaction: null, interactionTx: missingInteraction, currentTx });
|
||||
|
||||
// creating a Contract instance for the "writing" contract
|
||||
const writingContract = executionContext.warp.contract(
|
||||
writingContractTxId,
|
||||
executionContext.contract,
|
||||
missingInteraction
|
||||
);
|
||||
const writingContract = executionContext.warp.contract(writingContractTxId, executionContext.contract, {
|
||||
callingInteraction: missingInteraction,
|
||||
callType: 'read'
|
||||
});
|
||||
|
||||
await this.onContractCall(
|
||||
missingInteraction,
|
||||
|
||||
@@ -55,11 +55,10 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
|
||||
this.logger.debug('swGlobal.write call:', debugData);
|
||||
|
||||
// The contract that we want to call and modify its state
|
||||
const calleeContract = executionContext.warp.contract(
|
||||
contractTxId,
|
||||
executionContext.contract,
|
||||
this.swGlobal._activeTx
|
||||
);
|
||||
const calleeContract = executionContext.warp.contract(contractTxId, executionContext.contract, {
|
||||
callingInteraction: this.swGlobal._activeTx,
|
||||
callType: 'write'
|
||||
});
|
||||
|
||||
const result = await calleeContract.dryWriteFromTx<Input>(input, this.swGlobal._activeTx, [
|
||||
...(currentTx || []),
|
||||
@@ -104,11 +103,10 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
|
||||
to: contractTxId,
|
||||
input
|
||||
});
|
||||
const childContract = executionContext.warp.contract(
|
||||
contractTxId,
|
||||
executionContext.contract,
|
||||
this.swGlobal._activeTx
|
||||
);
|
||||
const childContract = executionContext.warp.contract(contractTxId, executionContext.contract, {
|
||||
callingInteraction: this.swGlobal._activeTx,
|
||||
callType: 'view'
|
||||
});
|
||||
|
||||
return await childContract.viewStateForTx(input, this.swGlobal._activeTx);
|
||||
};
|
||||
@@ -129,7 +127,10 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
|
||||
});
|
||||
|
||||
const { stateEvaluator } = executionContext.warp;
|
||||
const childContract = executionContext.warp.contract(contractTxId, executionContext.contract, interactionTx);
|
||||
const childContract = executionContext.warp.contract(contractTxId, executionContext.contract, {
|
||||
callingInteraction: interactionTx,
|
||||
callType: 'read'
|
||||
});
|
||||
|
||||
await stateEvaluator.onContractCall(interactionTx, executionContext, currentResult);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user