diff --git a/contracts/signature_mem_contract.fc b/contracts/signature_mem_contract.fc new file mode 100644 index 0000000..4039383 --- /dev/null +++ b/contracts/signature_mem_contract.fc @@ -0,0 +1,58 @@ +;; Signature Mem Contract +;; github.com/delpydoc | t.me/delpydoc +;; +;; Scheme +;; storage#_ issuer:MsgAddress query_id:uint256 = Storage; +;; status_new#BACA10 query_id:uint256 forward_payload:^InternalMsgBody = InternalMsgBody; +;; status_exist#BACA20 query_id:uint256 forward_payload:^InternalMsgBody = InternalMsgBody; + +#include "imports/stdlib.fc"; + +const int opcode::status_new = 0xBACA10; +const int opcode::status_exist = 0xBACA20; + +() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { + var cs = in_msg_full.begin_parse(); + cs~skip_bits(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + slice ctx::sender = cs~load_msg_addr(); + cs~load_msg_addr(); ;; skip dst + cs~load_coins(); ;; skip value + cs~skip_bits(1); ;; skip extracurrency collection + cs~load_coins(); ;; skip ihr_fee + raw_reserve(cs~load_coins(), 0); + slice ds = get_data().begin_parse(); + (slice c4::issuer, int c4::query_id) = (ds~load_msg_addr(), ds~load_uint(256)); + int ret_op = opcode::status_exist; + if (ds.slice_bits() == 0) { + ret_op = opcode::status_new; + } + slice ret_address = in_msg_body~load_msg_addr(); + cell forward_payload = in_msg_body~load_ref(); + send_raw_message( + ( + begin_cell() + .store_uint(0x10, 6) + .store_slice(c4::issuer) + .store_coins(0) + .store_uint(1, 1 + 4 + 4 + 32 + 64 + 1 + 1) + .store_ref( + begin_cell() + .store_uint(ret_op, 32) + .store_uint(c4::query_id, 256) + .store_slice(ret_address) + .store_ref(forward_payload) + .end_cell() + ) + .end_cell() + ), 128 + 2 ;; carry remaining gas + ignore errors + ); + set_data( + begin_cell() + .store_slice(c4::issuer) + .store_uint(c4::query_id, 256) + .store_int(true, 1) + .end_cell() + ); + return (); +} + diff --git a/contracts/universal_wallet_contract.fc b/contracts/universal_wallet_contract.fc index f5a9945..9783f91 100644 --- a/contracts/universal_wallet_contract.fc +++ b/contracts/universal_wallet_contract.fc @@ -2,7 +2,7 @@ ;; github.com/delpydoc | t.me/delpydoc ;; ;; Scheme -;; message_states#_ public_key:uint256 state_cleaned:uint64 old_queries:(HashmapE 14 ^Cell) = MessageStates; +;; message_states#_ public_key:uint256 mem_code:^Cell = MessageStates; ;; storage#_ subwallet_id:uint32 admin:MsgAddressInt message_states:MessageStates = Storage; #include "imports/stdlib.fc"; @@ -12,6 +12,11 @@ int gas_consumed() asm "GASCONSUMED"; const int opcode::admin::provide_message = 0xA173; const int opcode::admin::edit_code = 0xA183; const int opcode::any::provide_message = 0xA174; +const int opcode::mem::status_new = 0xBACA10; +const int opcode::mem::status_exist = 0xBACA20; + +const int opcode::mem::status_new = 0xBACA10; +const int opcode::mem::status_exist = 0xBACA20; const int error::privileges_violation = 72; const int error::no_gas = 76; @@ -19,6 +24,7 @@ const int error::expired_request = 78; const int error::unknown_op = 0xffff; const int env::query_timeout = 600; +const int env::workchain = 0; (cell) storage::pack(int subwallet_id, slice admin_address, cell message_states) inline_ref { return ( @@ -30,16 +36,40 @@ const int env::query_timeout = 600; ); } -(cell) message_states::pack(int public_key, int state_cleaned, cell old_queries) inline_ref { +(cell) message_states::pack(int public_key, cell mem_code) inline_ref { return ( begin_cell() .store_uint(public_key, 256) - .store_uint(state_cleaned, 64) - .store_dict(old_queries) + .store_ref(mem_code) .end_cell() ); } +(cell) mem_query::calculate_state_init(cell mem_code, int query_id) { + return ( + begin_cell() + .store_uint(6, 5) + .store_ref(mem_code) + .store_ref( + begin_cell() + .store_slice(my_address()) + .store_uint(query_id, 256) + .end_cell() + ) + .end_cell() + ); +} + +(slice) calculate_contract_address(cell mem_state_init) { + return ( + begin_cell() + .store_uint(4, 3) + .store_int(env::workchain, 8) + .store_uint(cell_hash(mem_state_init), 256) + .end_cell().begin_parse() + ); +} + () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { var cs = in_msg_full.begin_parse(); var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool @@ -76,25 +106,24 @@ const int env::query_timeout = 600; return (); } - if (ctx::op == opcode::any::provide_message) { + if (ctx::op == opcode::mem::status_new) { + int mem::query_id = in_msg_body~load_uint(256); slice c4::messages_state = ds~load_ref().begin_parse(); - (int c4::public_key, int c4::state_cleaned, cell c4::old_queries) = ( - c4::messages_state~load_uint(256), c4::messages_state~load_uint(64), c4::messages_state~load_dict() - ); + (int c4::public_key, cell c4::mem_contract_code) = (c4::messages_state~load_uint(256), c4::messages_state~load_ref()); + cell mem_state_init = mem_query::calculate_state_init(c4::mem_contract_code, mem::query_id); + slice mem_contract_address = calculate_contract_address(mem_state_init); + throw_unless(error::privileges_violation, equal_slices(mem_contract_address, ctx::sender)); + in_msg_body = in_msg_body~load_ref().begin_parse(); slice ctx::request_signature = in_msg_body~load_bits(512); int ctx::request_hash = slice_hash(in_msg_body); - throw_if(error::privileges_violation - 2, c4::public_key == 0); - throw_unless(error::privileges_violation - 3, check_signature(ctx::request_hash, ctx::request_signature, c4::public_key)); + throw_if(error::privileges_violation - 1, c4::public_key == 0); + throw_unless(error::privileges_violation - 2, check_signature(ctx::request_hash, ctx::request_signature, c4::public_key)); int ctx::query_id = in_msg_body~load_uint(64); + throw_unless(error::privileges_violation - 3, ctx::query_id == mem::query_id); int bound = (now() << 32); - ~dump([34, ctx::query_id >> 32, bound >> 32]); throw_if(error::expired_request, ctx::query_id < bound); - (_, int known_query?) = c4::old_queries.udict_get?(64, ctx::query_id); - throw_if(error::expired_request + 1, known_query?); - int ctx::subwallet_id = in_msg_body~load_uint(32); throw_unless(error::privileges_violation - 4, ctx::subwallet_id == c4::subwallet_id); - int ctx::request_gas = in_msg_body~load_coins(); throw_if(error::no_gas, msg_value < ctx::request_gas); accept_message(); @@ -102,26 +131,53 @@ const int env::query_timeout = 600; while (in_msg_body.slice_bits()) { int msg_mode = in_msg_body~load_uint(8); cell raw_msg = in_msg_body~load_ref(); - send_raw_message(raw_msg, (msg_mode | 2)); ;; maybe dont provide msg_mdoe + send_raw_message(raw_msg, (msg_mode | 2)); ;; maybe dont provide msg_mode } + return (); + } - c4::old_queries~udict_set_builder(64, ctx::query_id, begin_cell()); - bound -= (env::query_timeout << 32); - var queries = c4::old_queries; - do { - var (old_queries', i, _, f) = c4::old_queries.udict_delete_get_min(64); - f~touch(); - if (f) { - f = (i < bound); - } - if (f) { - c4::old_queries = old_queries'; - c4::state_cleaned = i; - } - } until (~ f); + if (ctx::op == opcode::mem::status_exist) { + int mem::query_id = in_msg_body~load_uint(256); + slice c4::messages_state = ds~load_ref().begin_parse(); + (int c4::public_key, cell c4::mem_contract_code) = (c4::messages_state~load_uint(256), c4::messages_state~load_ref()); + cell mem_state_init = mem_query::calculate_state_init(c4::mem_contract_code, mem::query_id); + slice mem_contract_address = calculate_contract_address(mem_state_init); + throw_unless(error::privileges_violation, equal_slices(mem_contract_address, ctx::sender)); + slice ret_address = in_msg_body~load_msg_addr(); + send_raw_message( + begin_cell() + .store_uint(0x10, 6) + .store_slice(ret_address) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 32 + 64 + 1 + 1) + .end_cell(), 64 + 2 + ); + return (); + } - cell new_message_states = message_states::pack(c4::public_key, c4::state_cleaned, c4::old_queries); - set_data(storage::pack(c4::subwallet_id, c4::admin_address, new_message_states)); + if (ctx::op == opcode::any::provide_message) { + slice raw_payload = in_msg_body; + in_msg_body~skip_bits(512); + int ctx::query_id = in_msg_body~load_uint(64); + slice c4::messages_state = ds~load_ref().begin_parse(); + (_, cell c4::mem_contract_code) = (c4::messages_state~load_uint(256), c4::messages_state~load_ref()); + cell mem_state_init = mem_query::calculate_state_init(c4::mem_contract_code, ctx::query_id); + slice mem_contract_address = calculate_contract_address(mem_state_init); + send_raw_message( + begin_cell() + .store_uint(0x18, 6) + .store_slice(mem_contract_address) + .store_coins(0) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 32 + 64 + 1 + 1 + 1) + .store_ref(mem_state_init) + .store_ref( + begin_cell() + .store_slice(ctx::sender) + .store_ref(begin_cell().store_slice(raw_payload).end_cell()) + .end_cell() + ) + .end_cell(), 64 + ); return (); } @@ -133,8 +189,10 @@ const int env::query_timeout = 600; return (ds~load_uint(32), ds~load_msg_addr()); } -(int, int, cell) get_message_states() method_id { +(int, cell) get_message_states() method_id { ;; -> (public_key, mem_code) slice ds = get_data().begin_parse(); slice messages_state = ds~load_ref().begin_parse(); - return (messages_state~load_uint(256), messages_state~load_uint(64), messages_state~load_dict()); + return (messages_state~load_uint(256), messages_state~load_ref()); } + + diff --git a/tests/UniversalWalletContract.spec.ts b/tests/UniversalWalletContract.spec.ts index 0618174..6f63ff0 100644 --- a/tests/UniversalWalletContract.spec.ts +++ b/tests/UniversalWalletContract.spec.ts @@ -1,4 +1,4 @@ -import {Blockchain, printTransactionFees, SandboxContract, TreasuryContract} from '@ton/sandbox'; +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'; @@ -17,9 +17,11 @@ describe('UniversalWalletContract', () => { 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; @@ -39,6 +41,7 @@ describe('UniversalWalletContract', () => { subwalletId: 3, publicKey: Buffer.from(privateKey.publicKey), admin: admin.address, + memCode: memCode }, code)); const deployResult = await universalWalletContract.sendDeploy(deployer.getSender(), toNano('10')); @@ -166,6 +169,8 @@ describe('UniversalWalletContract', () => { toNano('2'), signedRequest.endCell() ); + prettyLogTransactions(commandResult.transactions); + printTransactionFees(commandResult.transactions); expect(commandResult.transactions).toHaveTransaction({ to: someUser.address, from: universalWalletContract.address, @@ -177,10 +182,11 @@ describe('UniversalWalletContract', () => { toNano('2'), signedRequest.endCell() ); + prettyLogTransactions(dublicateCommandReuslt.transactions) + printTransactionFees(dublicateCommandReuslt.transactions); expect(dublicateCommandReuslt.transactions).toHaveTransaction({ to: universalWalletContract.address, - from: someUser.address, - exitCode: 79 + op: 0xbaca20 }); }); @@ -188,14 +194,15 @@ describe('UniversalWalletContract', () => { const newDataCell = universalWalletContractConfigToCell({ subwalletId: 3, publicKey: Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'), - admin: admin.address + admin: admin.address, + memCode: memCode }); - await blockchain.setVerbosityForAddress(universalWalletContract.address, { - blockchainLogs: true, - vmLogs: 'vm_logs', - print: true, - debugLogs: true - }) + // 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 diff --git a/wrappers/SignatureMemContract.compile.ts b/wrappers/SignatureMemContract.compile.ts new file mode 100644 index 0000000..0f5ef8c --- /dev/null +++ b/wrappers/SignatureMemContract.compile.ts @@ -0,0 +1,6 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'func', + targets: ['contracts/signature_mem_contract.fc'], +}; diff --git a/wrappers/UniversalWalletContract.ts b/wrappers/UniversalWalletContract.ts index 78f183c..d1cc855 100644 --- a/wrappers/UniversalWalletContract.ts +++ b/wrappers/UniversalWalletContract.ts @@ -3,7 +3,8 @@ import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, export type UniversalWalletContractConfig = { subwalletId: number, publicKey: Buffer, - admin: Address + admin: Address, + memCode: Cell }; export function universalWalletContractConfigToCell(config: UniversalWalletContractConfig): Cell { @@ -14,8 +15,7 @@ export function universalWalletContractConfigToCell(config: UniversalWalletContr .storeRef( beginCell() .storeBuffer(config.publicKey) - .storeUint(0, 64) - .storeUint(0, 1) + .storeRef(config.memCode) .endCell() ) .endCell(); @@ -97,8 +97,7 @@ export class UniversalWalletContract implements Contract { let stack = (await provider.get('get_message_states', [])).stack; return { publicKey: stack.readBigNumber(), - stateCleaned: stack.readNumber(), - oldQueries: stack.readCellOpt() + memCode: stack.readCell() }; } }