From 467cad9080211eac2e6b321e64e7683b69e72487 Mon Sep 17 00:00:00 2001 From: Pierre Seznec Date: Mon, 3 Jun 2024 14:47:41 +0200 Subject: [PATCH] vm-mock: get dynamuc caller/callee --- assembly/__tests__/vm-mock.spec.ts | 41 ++++++-- assembly/vm-mock/env.ts | 13 +-- vm-mock/vm.js | 157 ++++++++++++++++------------- 3 files changed, 123 insertions(+), 88 deletions(-) diff --git a/assembly/__tests__/vm-mock.spec.ts b/assembly/__tests__/vm-mock.spec.ts index 85d54686..54d37950 100644 --- a/assembly/__tests__/vm-mock.spec.ts +++ b/assembly/__tests__/vm-mock.spec.ts @@ -21,6 +21,7 @@ import { mockBalance, mockOriginOperationId, mockSetChainId, + mockTransferredCoins, setDeployContext, setLocalContext, } from '../vm-mock/env'; @@ -220,11 +221,12 @@ describe('Testing mocked Context', () => { expect(isDeployingContract()).toStrictEqual(true); }); - it('should set the same address for caller and callee when deploy context is set', () => { - setDeployContext('AS12BqZEQ6sByNCALLER'); + it('should update the caller address', () => { + const deployerAddr = 'AS12BqZEQ6sByNCALLER'; + setDeployContext(deployerAddr); - expect(caller()).toStrictEqual(new Address('AS12BqZEQ6sByNCALLER')); - expect(callee()).not.toStrictEqual(new Address('AS12BqZEQ6sByNCALLER')); + expect(caller()).toStrictEqual(new Address(deployerAddr)); + expect(callee()).toStrictEqual(contractAddress); }); it('should give a random distinct value to callerAddress when deploy context is set', () => { @@ -239,9 +241,7 @@ describe('Testing mocked Context', () => { setDeployContext(); - expect(caller()).not.toStrictEqual(callerAddress); - expect(caller()).not.toStrictEqual(callee()); - + expect(caller()).toStrictEqual(callerAddress); expect(callee()).toStrictEqual(contractAddress); }); @@ -332,6 +332,10 @@ describe('Testing mocked origin operation id', () => { }); describe('balance mock', () => { + afterEach(() => { + resetStorage(); + }); + it('mock balance', () => { mockBalance(testAddress.toString(), 9999); expect(balanceOf(testAddress.toString())).toBe(9999); @@ -342,6 +346,12 @@ describe('balance mock', () => { expect(balance()).toBe(9999); }); + it('mock contract balance with transferred coins', () => { + mockTransferredCoins(1000); + mockBalance(contractAddress.toString(), 9999); + expect(balance()).toBe(1000 + 9999); + }); + it('mock contract balance and preserve storage', () => { Storage.set('thekey', 'thevalue'); mockBalance(contractAddress.toString(), 9999); @@ -351,9 +361,15 @@ describe('balance mock', () => { }); describe('transfer coins', () => { + afterEach(() => { + resetStorage(); + }); + const amount = 666; it('contract transfer amount to user', () => { - const initialContractBalance = balance(); + const initialContractBalance = 987654321; + + mockBalance(contractAddress.toString(), 987654321); transferCoins(testAddress, amount); expect(balanceOf(testAddress.toString())).toBe(amount); @@ -382,4 +398,13 @@ describe('transfer coins', () => { expect(balance()).toBe(0); transferCoins(testAddress, amount); }); + + it('Use the caller transfered coins to process the transfer', () => { + mockBalance(contractAddress.toString(), 0); + expect(balance()).toBe(0); + mockTransferredCoins(amount); + expect(balance()).toBe(amount); + transferCoins(testAddress, amount); + expect(balance()).toBe(0); + }); }); diff --git a/assembly/vm-mock/env.ts b/assembly/vm-mock/env.ts index cd905501..2b60bac2 100644 --- a/assembly/vm-mock/env.ts +++ b/assembly/vm-mock/env.ts @@ -121,17 +121,10 @@ export declare function addAddressToLedger(address: string): void; export declare function mockAdminContext(isAdmin: bool): void; /** - * Emulate a deployment context by giving the write access to all contracts - * as well a emulating a deployment for all of them. + * Emulate a deployment context by giving the write access to called contract. + * If callerAddress is not passed, uses the current call stack caller address as the caller address. + * If callerAddress is passed, the call stack will be updated to have the given address as the caller address. * - * @remarks - * By default, the context as already callStack that emulates a deployment but has not write access. - * This function ensure that both are set. - * - * The deployment emulation is done by modifying the call stack to have different caller and callee addresses. - * - * If the given callerAddress is the same as the current contract address in the call stack, - * it is ignored to generate a different one. * * @param callerAddress - the optional caller address to use for the deployment emulation * diff --git a/vm-mock/vm.js b/vm-mock/vm.js index 312aafed..565df8c7 100644 --- a/vm-mock/vm.js +++ b/vm-mock/vm.js @@ -1,4 +1,4 @@ -/* eslint-disable new-cap */ +/* eslint-disable new-cap,jsdoc/require-returns */ const { createHash } = await import('node:crypto'); import { SigningKey, hashMessage } from 'ethers'; @@ -9,8 +9,8 @@ import sha3 from 'js-sha3'; */ // Those both addresses have been randomly generated -let callerAddress = 'AU12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq'; -let contractAddress = 'AS12BqZEQ6sByhRLyEuf0YbQmcF2PsDdkNNG1akBJu9XcjZA1eT'; +let defaultCallerAddress = 'AU12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq'; +let defaultContractAddress = 'AS12BqZEQ6sByhRLyEuf0YbQmcF2PsDdkNNG1akBJu9XcjZA1eT'; let mockedOriginOpId = ''; @@ -59,7 +59,7 @@ export function generateRandOpId() { return 'O1' + mixRandomChars(47); } -let callStack = callerAddress + ' , ' + contractAddress; +let callStack = defaultCallerAddress + ' , ' + defaultContractAddress; /** * Ledger and datastore @@ -80,28 +80,55 @@ let callStack = callerAddress + ' , ' + contractAddress; let ledger; let adminContext = false; +let webModule; + +const scCallMockStack = []; +let callCoins = 0n; // Default value, coins for a call +let chainIdMock = 77658366n; // Default value, chain id for Buildnet + /** * Reset the ledger */ function resetLedger() { ledger = new Map(); - ledger.set(callerAddress, { + ledger.set(defaultCallerAddress, { storage: new Map(), contract: '', - balance: BigInt(100000), + balance: 100_000n, }); - ledger.set(contractAddress, { + ledger.set(defaultContractAddress, { storage: new Map(), contract: '', - balance: BigInt(100000), + balance: 100_000n, }); + callCoins = 0n; + } -let webModule; +/** + * + */ +function getCaller() { + // get first elt of callStack + const callStackArray = callStack.split(' , '); + return callStackArray[0]; +} -const scCallMockStack = []; -let callCoins = BigInt(0); // Default value, coins for a call -let chainIdMock = BigInt(77658366); // Default value, chain id for Buildnet +/** + * + */ +function getCallee() { + // get last elt of callStack + const callStackArray = callStack.split(' , '); + return callStackArray[callStackArray.length - 1]; +} + +/** + * + */ +function getCalleeBalance() { + return BigInt(ledger.get(getCallee()).balance) + callCoins; +} /** * Create a mock vm to simulate calls and responses of Massa WebAssembly sdk. @@ -265,7 +292,7 @@ export default function createMockedABI( const key = ptrToUint8ArrayString(kPtr); const v = getArrayBuffer(vPtr); - const addressStorage = ledger.get(contractAddress).storage; + const addressStorage = ledger.get(getCallee()).storage; addressStorage.set(key, v); }, @@ -273,8 +300,9 @@ export default function createMockedABI( assembly_script_get_data(kPtr) { const key = ptrToUint8ArrayString(kPtr); - if (ledger.has(contractAddress)) { - const addressStorage = ledger.get(contractAddress).storage; + const callee = getCallee(); + if (ledger.has(callee)) { + const addressStorage = ledger.get(callee).storage; if (addressStorage.has(key)) { return newArrayBuffer(addressStorage.get(key)); @@ -282,7 +310,7 @@ export default function createMockedABI( ERROR('data entry not found'); } } else { - ERROR(`Address ${contractAddress} does not exist in ledger.`); + ERROR(`Address ${callee} does not exist in ledger.`); } }, @@ -292,9 +320,6 @@ export default function createMockedABI( assembly_script_change_call_stack(callstackPtr) { callStack = ptrToString(callstackPtr); - callerAddress = callStack.split(' , ')[0]; - contractAddress = - callStack.split(' , ').length > 1 ? callStack.split(' , ')[1] : ''; }, assembly_script_generate_event(msgPtr) { @@ -336,7 +361,7 @@ export default function createMockedABI( assembly_script_has_data(kPtr) { const key = ptrToUint8ArrayString(kPtr); - const addressStorage = ledger.get(contractAddress).storage; + const addressStorage = ledger.get(getCallee()).storage; return addressStorage.has(key); }, @@ -353,14 +378,15 @@ export default function createMockedABI( assembly_script_delete_data(kPtr) { const key = ptrToUint8ArrayString(kPtr); - if (ledger.has(contractAddress)) { - const addressStorage = ledger.get(contractAddress).storage; + const callee = getCallee(); + if (ledger.has(callee)) { + const addressStorage = ledger.get(callee).storage; if (!addressStorage.has(key)) { ERROR('data entry not found'); } addressStorage.delete(key); } else { - ERROR(`Address ${contractAddress} does not exist in ledger.`); + ERROR(`Address ${callee} does not exist in ledger.`); } }, @@ -382,15 +408,15 @@ export default function createMockedABI( }, assembly_script_append_data(kPtr, valuePtr) { - const address = contractAddress; + const callee = getCallee(); const key = ptrToUint8ArrayString(kPtr); const newValue = getArrayBuffer(valuePtr); - if (!ledger.has(address)) { - ERROR(`Address ${address} does not exist in ledger.`); + if (!ledger.has(callee)) { + ERROR(`Address ${callee} does not exist in ledger.`); } - const addressStorage = ledger.get(address).storage; + const addressStorage = ledger.get(callee).storage; if (!addressStorage.has(key)) { ERROR('data entry not found'); @@ -474,43 +500,37 @@ export default function createMockedABI( assembly_script_set_deploy_context(addrPtr) { adminContext = true; - // Ensure the caller address is different from the contract address - if (!addrPtr || ptrToString(addrPtr) === contractAddress) { - // generate a new address if it is the same as the contract address - callerAddress = generateDumbAddress(); - } else { - callerAddress = ptrToString(addrPtr); - } - - if (!ledger.has(callerAddress)) { - // add the new address to the ledger - ledger.set(callerAddress, { - storage: new Map(), - contract: '', - balance: BigInt(0), - }); + let caller = getCaller(); + if(addrPtr) { + caller = ptrToString(addrPtr); + if (!ledger.has(caller)) { + // add the new address to the ledger + ledger.set(caller, { + storage: new Map(), + contract: '', + balance: BigInt(0), + }); + } + // updating the callStack + callStack = caller + ' , ' + getCallee(); } - // updating the callStack - callStack = callerAddress + ' , ' + contractAddress; }, assembly_script_set_local_context(addrPtr) { adminContext = true; - if (!addrPtr) { - // if the address is not set, uses the current contract address as caller address - callerAddress = contractAddress; - } else { + let callerAddress = getCallee(); + if (addrPtr) { callerAddress = ptrToString(addrPtr); - contractAddress = callerAddress; - } - if (!ledger.has(callerAddress)) { - ledger.set(callerAddress, { - storage: new Map(), - contract: '', - balance: BigInt(0), - }); + if (!ledger.has(callerAddress)) { + ledger.set(callerAddress, { + storage: new Map(), + contract: '', + balance: BigInt(0), + }); + } } - callStack = callerAddress + ' , ' + contractAddress; + + callStack = callerAddress + ' , ' + callerAddress; }, assembly_script_caller_has_write_access() { @@ -552,7 +572,7 @@ export default function createMockedABI( }, assembly_script_get_keys(prefix) { - const addressStorage = ledger.get(contractAddress).storage; + const addressStorage = ledger.get(getCallee()).storage; let keysArr = Array.from(addressStorage.keys()); if (prefix) { @@ -567,7 +587,7 @@ export default function createMockedABI( assembly_script_has_op_key(kPtr) { const k = ptrToUint8ArrayString(kPtr); - const addressStorage = ledger.get(contractAddress).storage; + const addressStorage = ledger.get(getCallee()).storage; if (!addressStorage.has(k)) return newArrayBuffer(); @@ -575,7 +595,7 @@ export default function createMockedABI( }, assembly_script_get_op_keys() { - const addressStorage = ledger.get(contractAddress).storage; + const addressStorage = ledger.get(getCallee()).storage; const keysArr = Array.from(addressStorage.keys()); const keys = serializeKeys(keysArr); @@ -584,7 +604,7 @@ export default function createMockedABI( assembly_script_get_op_data(kPtr) { const k = ptrToUint8ArrayString(kPtr); - const addressStorage = ledger.get(contractAddress).storage; + const addressStorage = ledger.get(getCallee()).storage; if (!addressStorage.has(k)) return newArrayBuffer(); @@ -612,7 +632,7 @@ export default function createMockedABI( }, assembly_script_get_owned_addresses() { - return newString(`[ ${callerAddress} , ${contractAddress} ]`); + return newString(`[ ${getCaller()} , ${getCallee()} ]`); }, assembly_script_send_message( @@ -651,7 +671,7 @@ export default function createMockedABI( assembly_script_set_bytecode(bytecodePtr) { const bytecode = getArrayBuffer(bytecodePtr); - const addressLedger = ledger.get(contractAddress); + const addressLedger = ledger.get(getCallee()); addressLedger.contract = bytecode; }, @@ -668,7 +688,7 @@ export default function createMockedABI( }, assembly_script_get_bytecode() { - const addressLedger = ledger.get(contractAddress); + const addressLedger = ledger.get(getCallee()); return newArrayBuffer(addressLedger.contract); }, @@ -696,15 +716,12 @@ export default function createMockedABI( ledger.get(toAddress).balance = BigInt(0); } - // get last elt of callStack - const callStackArray = callStack.split(' , '); - const fromAddress = callStackArray[callStackArray.length - 1]; - + const fromAddress = getCallee(); if (!ledger.has(fromAddress)) { ERROR(`Sending address ${fromAddress} does not exist in ledger.`); } - const senderBalance = ledger.get(fromAddress).balance; + const senderBalance = getCalleeBalance(); if (senderBalance == undefined || senderBalance < BigInt(amount)) { ERROR('Not enough balance to transfer ' + amount + ' coins.'); } @@ -757,7 +774,7 @@ export default function createMockedABI( }, assembly_script_get_balance() { - return BigInt(ledger.get(contractAddress).balance) + callCoins; + return getCalleeBalance(); }, assembly_script_get_balance_for(aPtr) {