import {Blockchain, prettyLogTransactions, printTransactionFees, SandboxContract, TreasuryContract} from '@ton/sandbox'; import {beginCell, Cell, toNano} from '@ton/core'; import { UniversalWalletContract, universalWalletContractConfigToCell } from '../wrappers/UniversalWalletContract'; import '@ton/test-utils'; import { compile } from '@ton/blueprint'; import * as tweetnacl from 'tweetnacl-ts'; import BN from 'bn.js'; function shiftLeft32bitBigInt(num: bigint): bigint { const shiftAmount = BigInt(2) ** BigInt(32); return num * shiftAmount; } describe('UniversalWalletContract', () => { const privateKeyHex = '86d1016f097805357a751a6b1ec8691eddb62def4f09aac6966ca10c6da9113e37625a5e1bf4a9bb4e8e57adf780d02781ee2c2b80129dc6a90f23b01657f9d9'; const privateKey = tweetnacl.sign_keyPair_fromSecretKey(Buffer.from(privateKeyHex, 'hex')); let code: Cell; let memCode: Cell; beforeAll(async () => { code = await compile('UniversalWalletContract'); memCode = await compile('SignatureMemContract'); }); let blockchain: Blockchain; let deployer: SandboxContract; let admin: SandboxContract; let someUser: SandboxContract; let universalWalletContract: SandboxContract; beforeEach(async () => { blockchain = await Blockchain.create(); blockchain.now = 100; deployer = await blockchain.treasury('deployer'); admin = await blockchain.treasury('admin'); someUser = await blockchain.treasury('someUser'); universalWalletContract = blockchain.openContract(UniversalWalletContract.createFromConfig({ subwalletId: 3, publicKey: Buffer.from(privateKey.publicKey), admin: admin.address, memCode: memCode }, code)); const deployResult = await universalWalletContract.sendDeploy(deployer.getSender(), toNano('10')); expect(deployResult.transactions).toHaveTransaction({ from: deployer.address, to: universalWalletContract.address, deploy: true, success: true, exitCode: 0 }); }); it('should deploy', async () => { // the check is done inside beforeEach // blockchain and universalWalletContract are ready to use }); it('should provide admin message correctly', async () => { const commandResult = await universalWalletContract.sendAdminProvideMessage(admin.getSender(), toNano('0.05'), [ { rawMsg: (beginCell() .storeUint(0x18, 6) .storeAddress(someUser.address) .storeCoins(toNano('3')) .storeUint(0, 1 + 4 + 4 + 32 + 64 + 1 + 1) .endCell()), msgMode: 1 }, { rawMsg: (beginCell() .storeUint(0x18, 6) .storeAddress(someUser.address) .storeCoins(toNano('5')) .storeUint(0, 1 + 4 + 4 + 32 + 64 + 1 + 1) .endCell()), msgMode: 1 } ]); expect(commandResult.transactions).toHaveTransaction({ from: admin.address, to: universalWalletContract.address, deploy: false, success: true, exitCode: 0 }); expect(commandResult.transactions).toHaveTransaction({ from: universalWalletContract.address, to: someUser.address, value: toNano('3'), }); expect(commandResult.transactions).toHaveTransaction({ from: universalWalletContract.address, to: someUser.address, value: toNano('5'), }); }); it('should provide backend signed user message correctly', async () => { let packedRequest = beginCell(); let createdAt = Math.floor(Date.now() / 1000); packedRequest = packedRequest.storeUint(shiftLeft32bitBigInt(BigInt(createdAt + 45)), 64); packedRequest = packedRequest.storeUint(3, 32); // without 1 TON gas fee the message will not be sent and contract throw bounce packedRequest = packedRequest.storeCoins(toNano('1')); packedRequest = ( packedRequest .storeUint(1, 8) .storeRef( beginCell() .storeUint(0x18, 6) .storeAddress(someUser.address) .storeCoins(toNano('0.5')) .storeUint(0, 1 + 4 + 4 + 32 + 64 + 1 + 1) .storeUint(0x1248, 32) .endCell() ) ); const requestHash = new Uint8Array(packedRequest.endCell().hash()); let signedRequest = beginCell(); let requestSignature = Buffer.from(tweetnacl.sign_detached(requestHash, new Uint8Array(Buffer.from(privateKeyHex, 'hex')))); //, new Uint8Array([...privateKey.secretKey, ...privateKey.publicKey]))); signedRequest = signedRequest.storeBuffer(requestSignature); signedRequest = signedRequest.storeSlice(packedRequest.endCell().beginParse()); const commandResult = await universalWalletContract.sendSignedRequest( someUser.getSender(), toNano('2'), signedRequest.endCell() ); expect(commandResult.transactions).toHaveTransaction({ to: someUser.address, from: universalWalletContract.address, op: 0x1248, value: toNano('0.5') }); }); it('should reject duplicate signature send', async () => { let packedRequest = beginCell(); let createdAt = Math.floor(Date.now() / 1000); packedRequest = packedRequest.storeUint(shiftLeft32bitBigInt(BigInt(createdAt + 45)), 64); packedRequest = packedRequest.storeUint(3, 32); // without 1 TON gas fee the message will not be sent and contract throw bounce packedRequest = packedRequest.storeCoins(toNano('1')); packedRequest = ( packedRequest .storeUint(1, 8) .storeRef( beginCell() .storeUint(0x18, 6) .storeAddress(someUser.address) .storeCoins(toNano('0.5')) .storeUint(0, 1 + 4 + 4 + 32 + 64 + 1 + 1) .storeUint(0x1248, 32) .endCell() ) ); const requestHash = new Uint8Array(packedRequest.endCell().hash()); let signedRequest = beginCell(); let requestSignature = Buffer.from(tweetnacl.sign_detached(requestHash, new Uint8Array(Buffer.from(privateKeyHex, 'hex')))); //, new Uint8Array([...privateKey.secretKey, ...privateKey.publicKey]))); signedRequest = signedRequest.storeBuffer(requestSignature); signedRequest = signedRequest.storeSlice(packedRequest.endCell().beginParse()); const commandResult = await universalWalletContract.sendSignedRequest( someUser.getSender(), toNano('2'), signedRequest.endCell() ); prettyLogTransactions(commandResult.transactions); printTransactionFees(commandResult.transactions); expect(commandResult.transactions).toHaveTransaction({ to: someUser.address, from: universalWalletContract.address, op: 0x1248, value: toNano('0.5') }); const dublicateCommandReuslt = await universalWalletContract.sendSignedRequest( someUser.getSender(), toNano('2'), signedRequest.endCell() ); prettyLogTransactions(dublicateCommandReuslt.transactions) printTransactionFees(dublicateCommandReuslt.transactions); expect(dublicateCommandReuslt.transactions).toHaveTransaction({ to: universalWalletContract.address, op: 0xbaca20 }); }); it('should be updated publicKey', async () => { const newDataCell = universalWalletContractConfigToCell({ subwalletId: 3, publicKey: Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), admin: admin.address, memCode: memCode }); // await blockchain.setVerbosityForAddress(universalWalletContract.address, { // blockchainLogs: true, // vmLogs: 'vm_logs', // print: true, // debugLogs: true // }) const commandResult = await universalWalletContract.sendEditCode(admin.getSender(), toNano('0.1'), code, newDataCell); expect(commandResult.transactions).toHaveTransaction({ success: true }); const messagesState = await universalWalletContract.getMessageStates(); expect(messagesState.publicKey).toEqual(0n); }); return it('test big comissions', async () => { for (let i = 0; i < 151; i++) { let packedRequest = beginCell(); let createdAt = (i >= 149) ? 480 : 98; packedRequest = packedRequest.storeUint(shiftLeft32bitBigInt(BigInt(createdAt + 45)) + BigInt(i), 64); packedRequest = packedRequest.storeUint(3, 32); // without 1 TON gas fee the message will not be sent and contract throw bounce packedRequest = packedRequest.storeCoins(toNano('1')); packedRequest = ( packedRequest .storeUint(1, 8) .storeRef( beginCell() .storeUint(0x18, 6) .storeAddress(someUser.address) .storeCoins(toNano('0.5')) .storeUint(0, 1 + 4 + 4 + 32 + 64 + 1 + 1) .storeUint(0x1248, 32) .endCell() ) ); const requestHash = new Uint8Array(packedRequest.endCell().hash()); let signedRequest = beginCell(); let requestSignature = Buffer.from(tweetnacl.sign_detached(requestHash, new Uint8Array(Buffer.from(privateKeyHex, 'hex')))); //, new Uint8Array([...privateKey.secretKey, ...privateKey.publicKey]))); signedRequest = signedRequest.storeBuffer(requestSignature); signedRequest = signedRequest.storeSlice(packedRequest.endCell().beginParse()); if (i === 149) { blockchain.now = 500; } const commandResult = await universalWalletContract.sendSignedRequest( someUser.getSender(), toNano('2'), signedRequest.endCell() ); console.log(`Executing ${i}`); printTransactionFees(commandResult.transactions); expect(commandResult.transactions).toHaveTransaction({ to: someUser.address, from: universalWalletContract.address, op: 0x1248, value: toNano('0.5') }); } }); });