Skip to content

Commit

Permalink
feat: validate batch transaction into
Browse files Browse the repository at this point in the history
fix: rocky comments

fix: change filter to find

chore: update controller and provider

chore: bump provider version
  • Loading branch information
0xAaCE committed May 20, 2022
1 parent 2f6bdb7 commit 647bb9d
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 164 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.11.2",
"@psychedelic/dab-js": "1.3.2",
"@psychedelic/plug-controller": "../plug-controller",
"@psychedelic/plug-inpage-provider": "1.9.4",
"@psychedelic/plug-controller": "0.16.11",
"@psychedelic/plug-inpage-provider": "2.0.1",
"@reduxjs/toolkit": "^1.6.0",
"advanced-css-reset": "^1.2.2",
"axios": "^0.21.1",
Expand Down
3 changes: 3 additions & 0 deletions source/Background/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ export default {
ICNS_ERROR: { code: 400, message: 'There was an error trying to fetch your ICNS information.' },
CLIENT_ERROR: (message) => ({ code: 400, message }),
SERVER_ERROR: (message) => ({ code: 500, message }),
NOT_VALID_BATCH_TRANSACTION: {
code: 401, message: 'The transaction that was just attempted failed because it was not a valid batch transaction. Please contact the project’s developers.',
},
...SILENT_ERRORS,
};
61 changes: 55 additions & 6 deletions source/Background/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const validateBurnArgs = ({ to, amount }) => {

export const validateTransactions = (transactions) => Array.isArray(transactions)
&& transactions?.every(
(tx) => tx.idl && tx.canisterId && tx.methodName && tx.args,
(tx) => tx.sender && tx.canisterId && tx.methodName,
);

export const initializeProtectedIds = async () => {
Expand Down Expand Up @@ -109,23 +109,63 @@ export const fetchCanistersInfo = async (whitelist) => {
// TokenIdentifier is SYMBOL or CanisterID
// Return ICP by default
export const getToken = (tokenIdentifier, assets) => {
if (!tokenIdentifier) return assets.filter((asset) => asset.canisterId === ICP_CANISTER_ID)[0];
if (!tokenIdentifier) return assets.find((asset) => asset.canisterId === ICP_CANISTER_ID);

if (validateCanisterId(tokenIdentifier)) {
return assets.filter((asset) => asset.canisterId === tokenIdentifier)[0];
return assets.find((asset) => asset.canisterId === tokenIdentifier);
}

return assets.filter((asset) => asset.symbol === tokenIdentifier)[0];
return assets.find((asset) => asset.symbol === tokenIdentifier);
};
export const bufferToBase64 = (buf) => Buffer.from(buf).toString('base64');

export const base64ToBuffer = (base64) => Buffer.from(base64, 'base64');

export const isDeepEqualObject = (obj1, obj2) => JSON.stringify(obj1) === JSON.stringify(obj2);

export const validateBatchTx = (savedTxInfo, canisterId, methodName, arg) => {
if (!savedTxInfo
|| savedTxInfo.canisterId !== canisterId
|| savedTxInfo.methodName !== methodName) {
// if you dont have savedTxInfo
// or the methodName or cannotisterId is different from the savedTxInfo
// the batch tx is not valid
return false;
}

if (savedTxInfo.args) {
// if there is args saved in the savedTxInfo
// coming args must be the same as the saved args
// args and savedTxInfo.args gonna be base64 encoded
return savedTxInfo.args === arg;
}

return true;
};

export const handleCallRequest = async ({
keyring, request, callId, portId, callback, redirected,
}) => {
const arg = blobFromBuffer(base64ToBuffer(request.arguments));
try {
if (request.batchTxId && request.batchTxId.lenght !== 0 && !request.savedBatchTrx) {
callback(ERRORS.NOT_VALID_BATCH_TRANSACTION, null, [{ portId, callId }]);
if (redirected) callback(null, false);
return false;
}
if (request.savedBatchTrx) {
const validate = validateBatchTx(
request.savedBatchTrx,
request.canisterId,
request.methodName,
request.arguments,
);
if (!validate) {
callback(ERRORS.NOT_VALID_BATCH_TRANSACTION, null, [{ portId, callId }]);
if (redirected) callback(null, false);
return false;
}
}
const signed = await keyring
.getAgent().call(
Principal.fromText(request.canisterId),
Expand All @@ -141,16 +181,18 @@ export const handleCallRequest = async ({
{ callId, portId },
]);
if (redirected) callback(null, true);
return true;
} catch (e) {
callback(ERRORS.SERVER_ERROR(e), null, [{ portId, callId }]);
if (redirected) callback(null, false);
return false;
}
};

export const generateRequestInfo = (args) => {
const decodedArguments = Object.values(recursiveParseBigint(
const decodedArguments = recursiveParseBigint(
PlugController.IDLDecode(blobFromBuffer(base64ToBuffer(args.arg))),
));
);
return {
canisterId: args.canisterId,
methodName: args.methodName,
Expand All @@ -160,3 +202,10 @@ export const generateRequestInfo = (args) => {
type: 'call',
};
};

export const lebEncodeArgs = (args) => {
if (!Array.isArray(args) || args.length() === 0) {
return undefined;
}
return PlugController.IDLEncode(args);
};
174 changes: 48 additions & 126 deletions source/Modules/Controller/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import { getKeyringHandler, HANDLER_TYPES } from '@background/Keyring';
import { blobFromBuffer, blobToUint8Array } from '@dfinity/candid';

import SIZES from '../../Pages/Notification/components/Transfer/constants';
import { getApps, getProtectedIds } from '../storageManager';
import {
getBatchTransactions, getProtectedIds, setBatchTransactions, getApp,
} from '../storageManager';
import { ControllerModuleBase } from './controllerBase';

export class TransactionModule extends ControllerModuleBase {
Expand All @@ -42,8 +44,6 @@ export class TransactionModule extends ControllerModuleBase {
this.#handleRequestBurnXTC(),
this.#batchTransactions(),
TransactionModule.#handleBatchTransactions(),
this.#requestSign(),
this.#handleSign(),
this.#requestCall(),
this.#handleCall(),
this.#requestReadState(),
Expand All @@ -67,9 +67,7 @@ export class TransactionModule extends ControllerModuleBase {
const { id: callId } = message.data.data;
const { id: portId } = sender;

getApps(this.keyring?.currentWalletId.toString(), (apps = {}) => {
const app = apps?.[metadata?.url] || {};

getApp(this.keyring?.currentWalletId.toString(), metadata.url, app => {
if (app?.status === CONNECTION_STATUS.accepted) {
const argsError = validateTransferArgs(args);
if (argsError) {
Expand Down Expand Up @@ -156,9 +154,7 @@ export class TransactionModule extends ControllerModuleBase {
const { id: callId } = message.data.data;
const { id: portId } = sender;

getApps(this.keyring?.currentWalletId.toString(), async (apps = {}) => {
const app = apps?.[metadata?.url] || {};

getApp(this.keyring?.currentWalletId.toString(), metadata.url, async (app = {}) => {
if (app?.status === CONNECTION_STATUS.accepted) {
const argsError = validateTransferArgs(args);
if (argsError) {
Expand Down Expand Up @@ -264,8 +260,7 @@ export class TransactionModule extends ControllerModuleBase {
const { id: callId } = message.data.data;
const { id: portId } = sender;

getApps(this.keyring?.currentWalletId.toString(), async (apps = {}) => {
const app = apps?.[metadata.url] || {};
getApp(this.keyring?.currentWalletId.toString(), metadata.url, async (app = {}) => {
if (app?.status === CONNECTION_STATUS.accepted) {
const argsError = validateBurnArgs(args);
if (argsError) {
Expand Down Expand Up @@ -354,11 +349,9 @@ export class TransactionModule extends ControllerModuleBase {
const { id: callId } = message.data.data;
const { id: portId } = sender;

getApps(this.keyring?.currentWalletId.toString(), async (apps = {}) => {
const app = apps?.[metadata?.url] || {};

getApp(this.keyring?.currentWalletId.toString(), metadata.url, async (app = {}) => {
if (app?.status === CONNECTION_STATUS.accepted) {
const transactionsError = validateTransactions(transactions);
const transactionsError = !validateTransactions(transactions);

if (transactionsError) {
callback(transactionsError, null);
Expand Down Expand Up @@ -400,107 +393,25 @@ export class TransactionModule extends ControllerModuleBase {
static #handleBatchTransactions() {
return {
methodName: 'handleBatchTransactions',
handler: async (opts, accepted, callId, portId) => {
handler: async (opts, accepted, transactions, callId, portId) => {
const { callback } = opts;
if (accepted) {
callback(null, accepted, [{ callId, portId }]);
callback(null, true); // close the modal
} else {
callback(ERRORS.TRANSACTION_REJECTED, false, [{ callId, portId }]);
}
},
};
}

#requestSign() {
return {
methodName: 'requestSign',
handler: async (opts, payload, metadata, requestInfo) => {
const { message, sender, callback } = opts;
const { id: callId } = message.data.data;
const { id: portId } = sender;
const { canisterId, requestType, preApprove } = requestInfo;

try {
const isDangerousUpdateCall = !preApprove && requestType === 'call';
if (isDangerousUpdateCall) {
getApps(
this.keyring?.currentWalletId.toString(),
async (apps = {}) => {
const app = apps?.[metadata.url] || {};
if (app.status !== CONNECTION_STATUS.accepted) {
callback(ERRORS.CONNECTION_ERROR, null);
return;
}
if (canisterId && !(canisterId in app.whitelist)) {
callback(
ERRORS.CANISTER_NOT_WHITLESTED_ERROR(canisterId),
null,
);
return;
}
getProtectedIds(async (protectedIds) => {
const canisterInfo = app.whitelist[canisterId];
const shouldShowModal = protectedIds.includes(
canisterInfo.id,
);

if (shouldShowModal) {
const height = this.keyring?.isUnlocked
? SIZES.appConnectHeight
: SIZES.loginHeight;

this.displayPopUp({
callId,
portId,
type: 'sign',
metadataJson: JSON.stringify(metadata),
argsJson: JSON.stringify({
requestInfo,
payload,
canisterInfo,
timeout: app?.timeout,
}),
screenArgs: {
fixedHeight: height,
},
});
} else {
this.#signData(payload, callback);
}
});
},
);
} else {
this.#signData(payload, callback);
}
} catch (e) {
callback(ERRORS.SERVER_ERROR(e), null);
}
},
};
}

#handleSign() {
return {
methodName: 'handleSign',
handler: async (opts, status, request, callId, portId) => {
const { callback } = opts;

if (status === CONNECTION_STATUS.accepted) {
try {
const parsedPayload = new Uint8Array(Object.values(request.payload));

const signed = await this.keyring?.sign(parsedPayload.buffer);
callback(null, new Uint8Array(signed), [{ callId, portId }]);
callback(null, true);
} catch (e) {
callback(ERRORS.SERVER_ERROR(e), null, [{ portId, callId }]);
callback(null, false);
}
getBatchTransactions(async (err, batchTransactions) => {
const newBatchTransactionId = crypto.randomUUID();
const updatedBatchTransactions = {
...batchTransactions,
[newBatchTransactionId]: transactions
.map((tx) => ({
canisterId: tx.canisterId, methodName: tx.methodName, args: tx.arguments,
})),
};
setBatchTransactions(updatedBatchTransactions);

callback(null, { status: accepted, txId: newBatchTransactionId }, [{ callId, portId }]);
callback(null, true); // close the modal
});
} else {
callback(ERRORS.SIGN_REJECTED, null, [{ portId, callId }]);
callback(null, true); // Return true to close the modal
callback(ERRORS.TRANSACTION_REJECTED, { status: false }, [{ callId, portId }]);
}
},
};
Expand All @@ -509,7 +420,7 @@ export class TransactionModule extends ControllerModuleBase {
#requestCall() {
return {
methodName: 'requestCall',
handler: async (opts, metadata, args, preAprove) => {
handler: async (opts, metadata, args, batchTxId) => {
const { message, sender, callback } = opts;
const { id: callId } = message.data.data;
const { id: portId } = sender;
Expand All @@ -520,10 +431,10 @@ export class TransactionModule extends ControllerModuleBase {
]
.principal;
try {
getApps(
getApp(
this.keyring.currentWalletId.toString(),
async (apps = {}) => {
const app = apps?.[metadata.url] || {};
metadata.url,
async (app = {}) => {
if (app.status !== CONNECTION_STATUS.accepted) {
callback(ERRORS.CONNECTION_ERROR, null);
return;
Expand All @@ -537,7 +448,8 @@ export class TransactionModule extends ControllerModuleBase {
}
getProtectedIds(async (protectedIds) => {
const canisterInfo = app.whitelist[canisterId];
const shouldShowModal = !preAprove && protectedIds.includes(canisterInfo.id);
const shouldShowModal = (!batchTxId || batchTxId.lenght === 0)
&& protectedIds.includes(canisterInfo.id);
const requestInfo = generateRequestInfo({ ...args, sender: senderPID });

if (shouldShowModal) {
Expand All @@ -553,14 +465,24 @@ export class TransactionModule extends ControllerModuleBase {
metadataJson: JSON.stringify(metadata),
});
} else {
handleCallRequest({
keyring: this.keyring,
request: {
arguments: arg, methodName, canisterId,
},
portId,
callId,
callback,
getBatchTransactions((batchTransactions) => {
const savedBatchTrx = batchTxId
? batchTransactions[batchTxId].shift()
: undefined;

setBatchTransactions({
...batchTransactions,
}, () => {
handleCallRequest({
keyring: this.keyring,
request: {
arguments: arg, methodName, canisterId, savedBatchTrx, batchTxId,
},
portId,
callId,
callback,
});
});
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const useTransactions = (transactions, callId, portId) => {
setLoading(true);
await portRPC.call('handleBatchTransactions', [
accepted,
transactions,
callId,
portId,
]);
Expand Down
Loading

0 comments on commit 647bb9d

Please sign in to comment.