universal contract update
This commit is contained in:
parent
5b750b12cf
commit
b434447aef
|
|
@ -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 ();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
;; github.com/delpydoc | t.me/delpydoc
|
;; github.com/delpydoc | t.me/delpydoc
|
||||||
;;
|
;;
|
||||||
;; Scheme
|
;; 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;
|
;; storage#_ subwallet_id:uint32 admin:MsgAddressInt message_states:MessageStates = Storage;
|
||||||
|
|
||||||
#include "imports/stdlib.fc";
|
#include "imports/stdlib.fc";
|
||||||
|
|
@ -12,6 +12,11 @@ int gas_consumed() asm "GASCONSUMED";
|
||||||
const int opcode::admin::provide_message = 0xA173;
|
const int opcode::admin::provide_message = 0xA173;
|
||||||
const int opcode::admin::edit_code = 0xA183;
|
const int opcode::admin::edit_code = 0xA183;
|
||||||
const int opcode::any::provide_message = 0xA174;
|
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::privileges_violation = 72;
|
||||||
const int error::no_gas = 76;
|
const int error::no_gas = 76;
|
||||||
|
|
@ -19,6 +24,7 @@ const int error::expired_request = 78;
|
||||||
const int error::unknown_op = 0xffff;
|
const int error::unknown_op = 0xffff;
|
||||||
|
|
||||||
const int env::query_timeout = 600;
|
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 {
|
(cell) storage::pack(int subwallet_id, slice admin_address, cell message_states) inline_ref {
|
||||||
return (
|
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 (
|
return (
|
||||||
begin_cell()
|
begin_cell()
|
||||||
.store_uint(public_key, 256)
|
.store_uint(public_key, 256)
|
||||||
.store_uint(state_cleaned, 64)
|
.store_ref(mem_code)
|
||||||
.store_dict(old_queries)
|
|
||||||
.end_cell()
|
.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 {
|
() 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 cs = in_msg_full.begin_parse();
|
||||||
var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
|
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 ();
|
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();
|
slice c4::messages_state = ds~load_ref().begin_parse();
|
||||||
(int c4::public_key, int c4::state_cleaned, cell c4::old_queries) = (
|
(int c4::public_key, cell c4::mem_contract_code) = (c4::messages_state~load_uint(256), c4::messages_state~load_ref());
|
||||||
c4::messages_state~load_uint(256), c4::messages_state~load_uint(64), c4::messages_state~load_dict()
|
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);
|
slice ctx::request_signature = in_msg_body~load_bits(512);
|
||||||
int ctx::request_hash = slice_hash(in_msg_body);
|
int ctx::request_hash = slice_hash(in_msg_body);
|
||||||
throw_if(error::privileges_violation - 2, c4::public_key == 0);
|
throw_if(error::privileges_violation - 1, c4::public_key == 0);
|
||||||
throw_unless(error::privileges_violation - 3, check_signature(ctx::request_hash, ctx::request_signature, c4::public_key));
|
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);
|
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);
|
int bound = (now() << 32);
|
||||||
~dump([34, ctx::query_id >> 32, bound >> 32]);
|
|
||||||
throw_if(error::expired_request, ctx::query_id < bound);
|
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);
|
int ctx::subwallet_id = in_msg_body~load_uint(32);
|
||||||
throw_unless(error::privileges_violation - 4, ctx::subwallet_id == c4::subwallet_id);
|
throw_unless(error::privileges_violation - 4, ctx::subwallet_id == c4::subwallet_id);
|
||||||
|
|
||||||
int ctx::request_gas = in_msg_body~load_coins();
|
int ctx::request_gas = in_msg_body~load_coins();
|
||||||
throw_if(error::no_gas, msg_value < ctx::request_gas);
|
throw_if(error::no_gas, msg_value < ctx::request_gas);
|
||||||
accept_message();
|
accept_message();
|
||||||
|
|
@ -102,26 +131,53 @@ const int env::query_timeout = 600;
|
||||||
while (in_msg_body.slice_bits()) {
|
while (in_msg_body.slice_bits()) {
|
||||||
int msg_mode = in_msg_body~load_uint(8);
|
int msg_mode = in_msg_body~load_uint(8);
|
||||||
cell raw_msg = in_msg_body~load_ref();
|
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());
|
if (ctx::op == opcode::mem::status_exist) {
|
||||||
bound -= (env::query_timeout << 32);
|
int mem::query_id = in_msg_body~load_uint(256);
|
||||||
var queries = c4::old_queries;
|
slice c4::messages_state = ds~load_ref().begin_parse();
|
||||||
do {
|
(int c4::public_key, cell c4::mem_contract_code) = (c4::messages_state~load_uint(256), c4::messages_state~load_ref());
|
||||||
var (old_queries', i, _, f) = c4::old_queries.udict_delete_get_min(64);
|
cell mem_state_init = mem_query::calculate_state_init(c4::mem_contract_code, mem::query_id);
|
||||||
f~touch();
|
slice mem_contract_address = calculate_contract_address(mem_state_init);
|
||||||
if (f) {
|
throw_unless(error::privileges_violation, equal_slices(mem_contract_address, ctx::sender));
|
||||||
f = (i < bound);
|
slice ret_address = in_msg_body~load_msg_addr();
|
||||||
}
|
send_raw_message(
|
||||||
if (f) {
|
begin_cell()
|
||||||
c4::old_queries = old_queries';
|
.store_uint(0x10, 6)
|
||||||
c4::state_cleaned = i;
|
.store_slice(ret_address)
|
||||||
}
|
.store_coins(0)
|
||||||
} until (~ f);
|
.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);
|
if (ctx::op == opcode::any::provide_message) {
|
||||||
set_data(storage::pack(c4::subwallet_id, c4::admin_address, new_message_states));
|
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 ();
|
return ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,8 +189,10 @@ const int env::query_timeout = 600;
|
||||||
return (ds~load_uint(32), ds~load_msg_addr());
|
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 ds = get_data().begin_parse();
|
||||||
slice messages_state = ds~load_ref().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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {beginCell, Cell, toNano} from '@ton/core';
|
||||||
import { UniversalWalletContract, universalWalletContractConfigToCell } from '../wrappers/UniversalWalletContract';
|
import { UniversalWalletContract, universalWalletContractConfigToCell } from '../wrappers/UniversalWalletContract';
|
||||||
import '@ton/test-utils';
|
import '@ton/test-utils';
|
||||||
|
|
@ -17,9 +17,11 @@ describe('UniversalWalletContract', () => {
|
||||||
const privateKey = tweetnacl.sign_keyPair_fromSecretKey(Buffer.from(privateKeyHex, 'hex'));
|
const privateKey = tweetnacl.sign_keyPair_fromSecretKey(Buffer.from(privateKeyHex, 'hex'));
|
||||||
|
|
||||||
let code: Cell;
|
let code: Cell;
|
||||||
|
let memCode: Cell;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
code = await compile('UniversalWalletContract');
|
code = await compile('UniversalWalletContract');
|
||||||
|
memCode = await compile('SignatureMemContract');
|
||||||
});
|
});
|
||||||
|
|
||||||
let blockchain: Blockchain;
|
let blockchain: Blockchain;
|
||||||
|
|
@ -39,6 +41,7 @@ describe('UniversalWalletContract', () => {
|
||||||
subwalletId: 3,
|
subwalletId: 3,
|
||||||
publicKey: Buffer.from(privateKey.publicKey),
|
publicKey: Buffer.from(privateKey.publicKey),
|
||||||
admin: admin.address,
|
admin: admin.address,
|
||||||
|
memCode: memCode
|
||||||
}, code));
|
}, code));
|
||||||
|
|
||||||
const deployResult = await universalWalletContract.sendDeploy(deployer.getSender(), toNano('10'));
|
const deployResult = await universalWalletContract.sendDeploy(deployer.getSender(), toNano('10'));
|
||||||
|
|
@ -166,6 +169,8 @@ describe('UniversalWalletContract', () => {
|
||||||
toNano('2'),
|
toNano('2'),
|
||||||
signedRequest.endCell()
|
signedRequest.endCell()
|
||||||
);
|
);
|
||||||
|
prettyLogTransactions(commandResult.transactions);
|
||||||
|
printTransactionFees(commandResult.transactions);
|
||||||
expect(commandResult.transactions).toHaveTransaction({
|
expect(commandResult.transactions).toHaveTransaction({
|
||||||
to: someUser.address,
|
to: someUser.address,
|
||||||
from: universalWalletContract.address,
|
from: universalWalletContract.address,
|
||||||
|
|
@ -177,10 +182,11 @@ describe('UniversalWalletContract', () => {
|
||||||
toNano('2'),
|
toNano('2'),
|
||||||
signedRequest.endCell()
|
signedRequest.endCell()
|
||||||
);
|
);
|
||||||
|
prettyLogTransactions(dublicateCommandReuslt.transactions)
|
||||||
|
printTransactionFees(dublicateCommandReuslt.transactions);
|
||||||
expect(dublicateCommandReuslt.transactions).toHaveTransaction({
|
expect(dublicateCommandReuslt.transactions).toHaveTransaction({
|
||||||
to: universalWalletContract.address,
|
to: universalWalletContract.address,
|
||||||
from: someUser.address,
|
op: 0xbaca20
|
||||||
exitCode: 79
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -188,14 +194,15 @@ describe('UniversalWalletContract', () => {
|
||||||
const newDataCell = universalWalletContractConfigToCell({
|
const newDataCell = universalWalletContractConfigToCell({
|
||||||
subwalletId: 3,
|
subwalletId: 3,
|
||||||
publicKey: Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'),
|
publicKey: Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'),
|
||||||
admin: admin.address
|
admin: admin.address,
|
||||||
|
memCode: memCode
|
||||||
});
|
});
|
||||||
await blockchain.setVerbosityForAddress(universalWalletContract.address, {
|
// await blockchain.setVerbosityForAddress(universalWalletContract.address, {
|
||||||
blockchainLogs: true,
|
// blockchainLogs: true,
|
||||||
vmLogs: 'vm_logs',
|
// vmLogs: 'vm_logs',
|
||||||
print: true,
|
// print: true,
|
||||||
debugLogs: true
|
// debugLogs: true
|
||||||
})
|
// })
|
||||||
const commandResult = await universalWalletContract.sendEditCode(admin.getSender(), toNano('0.1'), code, newDataCell);
|
const commandResult = await universalWalletContract.sendEditCode(admin.getSender(), toNano('0.1'), code, newDataCell);
|
||||||
expect(commandResult.transactions).toHaveTransaction({
|
expect(commandResult.transactions).toHaveTransaction({
|
||||||
success: true
|
success: true
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { CompilerConfig } from '@ton/blueprint';
|
||||||
|
|
||||||
|
export const compile: CompilerConfig = {
|
||||||
|
lang: 'func',
|
||||||
|
targets: ['contracts/signature_mem_contract.fc'],
|
||||||
|
};
|
||||||
|
|
@ -3,7 +3,8 @@ import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider,
|
||||||
export type UniversalWalletContractConfig = {
|
export type UniversalWalletContractConfig = {
|
||||||
subwalletId: number,
|
subwalletId: number,
|
||||||
publicKey: Buffer,
|
publicKey: Buffer,
|
||||||
admin: Address
|
admin: Address,
|
||||||
|
memCode: Cell
|
||||||
};
|
};
|
||||||
|
|
||||||
export function universalWalletContractConfigToCell(config: UniversalWalletContractConfig): Cell {
|
export function universalWalletContractConfigToCell(config: UniversalWalletContractConfig): Cell {
|
||||||
|
|
@ -14,8 +15,7 @@ export function universalWalletContractConfigToCell(config: UniversalWalletContr
|
||||||
.storeRef(
|
.storeRef(
|
||||||
beginCell()
|
beginCell()
|
||||||
.storeBuffer(config.publicKey)
|
.storeBuffer(config.publicKey)
|
||||||
.storeUint(0, 64)
|
.storeRef(config.memCode)
|
||||||
.storeUint(0, 1)
|
|
||||||
.endCell()
|
.endCell()
|
||||||
)
|
)
|
||||||
.endCell();
|
.endCell();
|
||||||
|
|
@ -97,8 +97,7 @@ export class UniversalWalletContract implements Contract {
|
||||||
let stack = (await provider.get('get_message_states', [])).stack;
|
let stack = (await provider.get('get_message_states', [])).stack;
|
||||||
return {
|
return {
|
||||||
publicKey: stack.readBigNumber(),
|
publicKey: stack.readBigNumber(),
|
||||||
stateCleaned: stack.readNumber(),
|
memCode: stack.readCell()
|
||||||
oldQueries: stack.readCellOpt()
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue