Skip to content

Commit

Permalink
Added throw for unknown ext-act, setcode/data neg tests, test-only acts
Browse files Browse the repository at this point in the history
  • Loading branch information
Skydev0h committed Feb 15, 2024
1 parent c724855 commit 4bf0feb
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 338 deletions.
3 changes: 3 additions & 0 deletions contracts/wallet_v5.fc
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ cell verify_actions(cell c5) inline {
set_data(cs~load_ref());
}
-}
else {
throw(41); ;; unsupported action
}
;; Other actions are no-op
;; FIXME: is it costlier to check for unsupported actions and throw?
cs = cs.preload_ref().begin_parse();
Expand Down
9 changes: 6 additions & 3 deletions tests/actions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Address, beginCell, Cell, MessageRelaxed, SendMode, storeMessageRelaxed } from 'ton-core';
import { isTestOnlyExtendedAction, TestOnlyExtendedAction, TestOnlyOutAction } from './test-only-actions';

export class ActionSendMsg {
public static readonly tag = 0x0ec3c86d;
Expand Down Expand Up @@ -55,17 +56,19 @@ export class ActionSetSignatureAuthAllowed {
}
}

export type OutAction = ActionSendMsg;
export type OutAction = ActionSendMsg | TestOnlyOutAction;
export type ExtendedAction =
| ActionAddExtension
| ActionRemoveExtension
| ActionSetSignatureAuthAllowed;
| ActionSetSignatureAuthAllowed
| TestOnlyExtendedAction;

export function isExtendedAction(action: OutAction | ExtendedAction): action is ExtendedAction {
return (
action.tag === ActionAddExtension.tag ||
action.tag === ActionRemoveExtension.tag ||
action.tag === ActionSetSignatureAuthAllowed.tag
action.tag === ActionSetSignatureAuthAllowed.tag ||
isTestOnlyExtendedAction(action)
);
}

Expand Down
82 changes: 82 additions & 0 deletions tests/test-only-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
Address,
beginCell,
Cell,
CurrencyCollection,
MessageRelaxed,
SendMode,
storeCurrencyCollection,
storeMessageRelaxed
} from 'ton-core';
import {
ExtendedAction,
OutAction
} from './actions';

export type LibRef = Cell | bigint;

export class ActionSetCode {
public static readonly tag = 0xad4de08e;

public readonly tag = ActionSetCode.tag;

constructor(public readonly newCode: Cell) {}

public serialize(): Cell {
return beginCell().storeUint(this.tag, 32).storeRef(this.newCode).endCell();
}
}

export class ActionReserveCurrency {
public static readonly tag = 0x36e6b809;

public readonly tag = ActionReserveCurrency.tag;

constructor(public readonly mode: SendMode, public readonly currency: CurrencyCollection) {}

public serialize(): Cell {
return beginCell()
.storeUint(this.tag, 32)
.storeUint(this.mode, 8)
.store(storeCurrencyCollection(this.currency))
.endCell();
}
}

export class ActionChangeLibrary {
public static readonly tag = 0x26fa1dd4;

public readonly tag = ActionChangeLibrary.tag;

constructor(public readonly mode: number, public readonly libRef: LibRef) {}

public serialize(): Cell {
const cell = beginCell().storeUint(this.tag, 32).storeUint(this.mode, 7);
if (typeof this.libRef === 'bigint') {
return cell.storeUint(0, 1).storeUint(this.libRef, 256).endCell();
}

return cell.storeUint(1, 1).storeRef(this.libRef).endCell();
}
}

export class ActionSetData {
public static readonly tag = 0x1ff8ea0b;

public readonly tag = ActionSetData.tag;

constructor(public readonly data: Cell) {}

public serialize(): Cell {
return beginCell().storeUint(this.tag, 32).storeRef(this.data).endCell();
}
}

export type TestOnlyOutAction = ActionSetCode | ActionReserveCurrency | ActionChangeLibrary;
export type TestOnlyExtendedAction = ActionSetData;

export function isTestOnlyExtendedAction(action: OutAction | ExtendedAction): action is ExtendedAction {
return (
action.tag === ActionSetData.tag
);
}
193 changes: 27 additions & 166 deletions tests/wallet-v5-external.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { TransactionDescriptionGeneric } from 'ton-core/src/types/TransactionDes
import { TransactionComputeVm } from 'ton-core/src/types/TransactionComputePhase';
import { buildBlockchainLibraries, LibraryDeployer } from '../wrappers/library-deployer';
import { default as config } from './config';
import { ActionSetCode, ActionSetData } from './test-only-actions';
import { WalletV4 } from '../wrappers/wallet-v4';

const WALLET_ID = new WalletId({ networkGlobalId: -239, workChain: -1, subwalletNumber: 0 });

Expand Down Expand Up @@ -339,108 +341,6 @@ describe('Wallet V5 sign auth external', () => {
);
});

/* TODO: Rewrite as a negative test
it('Set data and do two transfers', async () => {
const testReceiver1 = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y');
const forwardValue1 = toNano(0.001);
const testReceiver2 = Address.parse('EQCgYDKqfTh7zVj9BQwOIPs4SuOhM7wnIjb6bdtM2AJf_Z9G');
const forwardValue2 = toNano(0.0012);
const receiver1BalanceBefore = (await blockchain.getContract(testReceiver1)).balance;
const receiver2BalanceBefore = (await blockchain.getContract(testReceiver2)).balance;
const msg1 = createMsgInternal({ dest: testReceiver1, value: forwardValue1 });
const msg2 = createMsgInternal({ dest: testReceiver2, value: forwardValue2 });
const actionsList = packActionsList([
new ActionSetData(beginCell().storeInt(239, 33).endCell()),
new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg1),
new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg2)
]);
const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList));
expect(receipt.transactions.length).toEqual(3);
accountForGas(receipt.transactions);
expect(receipt.transactions).toHaveTransaction({
from: walletV5.address,
to: testReceiver1,
value: forwardValue1
});
expect(receipt.transactions).toHaveTransaction({
from: walletV5.address,
to: testReceiver2,
value: forwardValue2
});
const fee1 = receipt.transactions[1].totalFees.coins;
const fee2 = receipt.transactions[2].totalFees.coins;
const receiver1BalanceAfter = (await blockchain.getContract(testReceiver1)).balance;
const receiver2BalanceAfter = (await blockchain.getContract(testReceiver2)).balance;
expect(receiver1BalanceAfter).toEqual(receiver1BalanceBefore + forwardValue1 - fee1);
expect(receiver2BalanceAfter).toEqual(receiver2BalanceBefore + forwardValue2 - fee2);
const storedSeqno = await walletV5.getSeqno();
expect(storedSeqno).toEqual(239);
});
*/

/* TODO: Rewrite as a negative test
it('Send 255 transfers and do set data', async () => {
await (
await blockchain.treasury('mass-messages')
).send({ to: walletV5.address, value: toNano(100) });
const range = [...new Array(255)].map((_, index) => index);
const receivers = range.map(i => Address.parseRaw('0:' + i.toString().padStart(64, '0')));
const balancesBefore = (
await Promise.all(receivers.map(r => blockchain.getContract(r)))
).map(i => i.balance);
const forwardValues = range.map(i => BigInt(toNano(0.000001 * i)));
const msges = receivers.map((dest, i) =>
createMsgInternal({ dest: dest, value: forwardValues[i] })
);
const actionsList = packActionsList([
new ActionSetData(beginCell().storeInt(239, 33).endCell()),
...msges.map(msg => new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg))
]);
const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList));
expect(receipt.transactions.length).toEqual(range.length + 1);
accountForGas(receipt.transactions);
receivers.forEach((to, i) => {
expect(receipt.transactions).toHaveTransaction({
from: walletV5.address,
to,
value: forwardValues[i]
});
});
const balancesAfter = (
await Promise.all(receivers.map(r => blockchain.getContract(r)))
).map(i => i.balance);
const fees = receipt.transactions.slice(1).map(tx => tx.totalFees.coins);
balancesAfter.forEach((balanceAfter, i) => {
expect(balanceAfter).toEqual(balancesBefore[i] + forwardValues[i] - fees[i]);
});
const storedSeqno = await walletV5.getSeqno();
expect(storedSeqno).toEqual(239);
});
*/

it('Remove extension', async () => {
const testExtension = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y');

Expand Down Expand Up @@ -471,76 +371,37 @@ describe('Wallet V5 sign auth external', () => {
accountForGas(receipt2.transactions);
});

/* TODO: Rewrite as a negative test
it('Change code and data to wallet v4', async () => {
const code_v4 = await compile('wallet_v4');
const data_v4 = beginCell()
.storeUint(0, 32)
.storeUint(0, 32)
.storeBuffer(keypair.publicKey, 32)
.storeDict(Dictionary.empty())
.endCell();
it('Should fail SetData action', async () => {
const cell = beginCell().endCell();

const actionsList = packActionsList([
new ActionSetData(data_v4),
new ActionSetCode(code_v4)
new ActionSetData(cell)
]);
const receipt1 = await walletV5.sendExternalSignedMessage(createBody(actionsList));
accountForGas(receipt1.transactions);
const walletV4 = blockchain.openContract(WalletV4.createFromAddress(walletV5.address));
const seqno = await walletV4.getSeqno();
const subwalletId = await walletV4.getSubWalletID();
const publicKey = await walletV4.getPublicKey();
const extensions = Dictionary.loadDirect(
Dictionary.Keys.Address(),
Dictionary.Values.BigInt(0),
await walletV4.getExtensions()
);
expect(seqno).toEqual(0);
expect(subwalletId).toEqual(0);
expect(publicKey).toEqual(bufferToBigInt(keypair.publicKey));
expect(extensions.size).toEqual(0);
const testReceiver = Address.parse('EQAvDfWFG0oYX19jwNDNBBL1rKNT9XfaGP9HyTb5nb2Eml6y');
const forwardValue = toNano(0.001);
const sendTxMsg = beginCell()
.storeUint(0x10, 6)
.storeAddress(testReceiver)
.storeCoins(forwardValue)
.storeUint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.storeRef(beginCell().endCell())
.endCell();
const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList));

const mesagesCell = beginCell()
.storeUint(0, 8)
.storeUint(SendMode.PAY_GAS_SEPARATELY, 8)
.storeRef(sendTxMsg)
.endCell();
expect(
(
(receipt.transactions[0].description as TransactionDescriptionGeneric)
.computePhase as TransactionComputeVm
).exitCode
).toEqual(41);
});

const payload = beginCell()
.storeUint(0, 32)
.storeUint(validUntil(), 32)
.storeUint(0, 32)
.storeSlice(mesagesCell.beginParse())
.endCell();
it('Should fail SetCode action', async () => {
const cell = beginCell().endCell();

const signature = sign(payload.hash(), keypair.secretKey);
const body = beginCell()
.storeUint(bufferToBigInt(signature), 512)
.storeSlice(payload.beginParse())
.endCell();
const actionsList = packActionsList([
new ActionSetCode(cell)
]);
const receipt = await walletV5.sendExternalSignedMessage(createBody(actionsList));

const receipt = await walletV4.sendExternalSignedMessage(body);
expect(receipt.transactions).toHaveTransaction({
from: walletV5.address,
to: testReceiver,
value: forwardValue
});
expect(
(
(receipt.transactions[0].description as TransactionDescriptionGeneric)
.computePhase as TransactionComputeVm
).exitCode
).toEqual(9);
});
*/

it('Should fail adding existing extension', async () => {
const testExtension = Address.parseRaw('0:' + '0'.repeat(64));
Expand Down Expand Up @@ -775,7 +636,7 @@ describe('Wallet V5 sign auth external', () => {
const msg = createMsgInternal({ dest: testReceiver, value: forwardValue });
const actionsList = packActionsList([new ActionSendMsg(SendMode.PAY_GAS_SEPARATELY, msg)]);

const payload = beginCell() // TODO: Seems to pass with auth_signed, need analysis!
const payload = beginCell() // auth_signed_internal used instead of auth_signed
.storeUint(Opcodes.auth_signed_internal, 32)
.storeUint(WALLET_ID.serialized, 80)
.storeUint(validUntil(), 32)
Expand All @@ -792,7 +653,7 @@ describe('Wallet V5 sign auth external', () => {
await disableConsoleError(() =>
expect(
walletV5.sendExternal(
beginCell().storeUint(1111, 32).storeSlice(body.beginParse()).endCell()
beginCell().storeSlice(body.beginParse()).endCell()
)
).rejects.toThrow()
);
Expand Down
Loading

0 comments on commit 4bf0feb

Please sign in to comment.