From ba320071c08537a00740f674d50b70fb8759b930 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Thu, 7 Nov 2024 10:06:12 +0100 Subject: [PATCH 01/23] chore(adapter-non-local.test.js): simplify variable names --- cpp/test/adapter-non-local.test.js | 693 ++++++++--------------------- 1 file changed, 188 insertions(+), 505 deletions(-) diff --git a/cpp/test/adapter-non-local.test.js b/cpp/test/adapter-non-local.test.js index f0eb96c..d956a46 100644 --- a/cpp/test/adapter-non-local.test.js +++ b/cpp/test/adapter-non-local.test.js @@ -1,72 +1,65 @@ const { expect } = require('chai') -const { Blockchain, expectToThrow, mintTokens } = require('@eosnetwork/vert') -const { deploy } = require('./utils/deploy') +const { Blockchain, expectToThrow } = require('@eosnetwork/vert') const { Asset } = require('@wharfkit/antelope') -const R = require('ramda') +const { Symbol } = Asset const { + sum, + no0x, active, - precision, - getAccountCodeRaw, - getSymbolCodeRaw, - getSingletonInstance, -} = require('./utils/eos-ext') -const { no0x, hexStringToBytes } = require('./utils/bytes-utils') -const { sum } = require('./utils/wharfkit-ext') -const { getAccountsBalances } = require('./utils/get-token-balance') -const errors = require('./utils/errors') - -const ethers = require('ethers') + deploy, + errors, + bytes32, + getSwapMemo, + getOperation, + serializeOperation, + getAccountsBalances, + fromEthersPublicKey, + substract, +} = require('./utils') const { - evmOperationSamples, - evmTopicZero, - evmAdapter, -} = require('./samples/evm-operations') -const { evmMetadataSamples, teePubKey } = require('./samples/evm-metadata') -const { adjustPrecision, fromWei } = require('./utils/precision-utils') - -const attestation = 'deadbeef' - -describe('Adapter EVM -> EOS testing', () => { - const evmTokenSymbol = 'TST' - const maxSupply = '500000000' - const evmPrecision = 18 - const evmXERC20Precision = 8 - - const TABLE_STORAGE = 'storage' - - // infos of the underlying token - const evmUnderlyingToken = { - symbol: evmTokenSymbol, - precision: evmPrecision, // this should be the actual precision of the underlying token - account: '', - maxSupply: `${adjustPrecision(maxSupply, evmXERC20Precision)} ${evmTokenSymbol}`, // use evmXERC20Precision to avoid overflow - bytes: evmOperationSamples.pegin.token, - } + Chains, + Versions, + Protocols, + ProofcastEventAttestator, +} = require('@pnetwork/event-attestator') + +describe('Adapter Testing - Non Local Deployment', () => { + const symbol = 'TST' + const xsymbol = `X${symbol}` + const maxSupply = 500000000 + const minFee = 0.0018 + const precision = 8 + const symbolPrecision = Symbol.fromParts(symbol, precision) + const xsymbolPrecision = Symbol.fromParts(xsymbol, precision) - const evmXERC20 = { - symbol: `X${evmTokenSymbol}`, - precision: evmXERC20Precision, // different from the underlying token - only uint64 is supported by Asset type - account: `x${evmTokenSymbol.toLowerCase()}.token`, - maxSupply: `${adjustPrecision(maxSupply, evmXERC20Precision)} X${evmTokenSymbol}`, - minFee: `${adjustPrecision('0.0018', evmXERC20Precision)} X${evmTokenSymbol}`, - contract: null, - } + const blockchain = new Blockchain() - const wrongToken = { - symbol: 'WRG', - account: 'wrg.token', - maxSupply: `${maxSupply} WRG`, - bytes: no0x( - ethers.zeroPadValue( - ethers.toBeHex(getSymbolCodeRaw('0.0000 WRG').toString()), - 32, - ), - ), - contract: null, + const user = 'user' + const evil = 'evil' + const issuer = 'issuer' + const bridge = 'bridge' + const recipient = 'eosrecipient' + const feemanager = 'feemanager' + + const evmSwapAmount = 5.87190615 + const evmOriginChainId = Chains(Protocols.Evm).Mainnet + const evmAdapter = + '000000000000000000000000bcf063a9eb18bc3c6eb005791c61801b7cb16fe4' + const evmTopicZero = + '66756e6473206172652073616675207361667520736166752073616675202e2e' + + const token = { + symbol, + account: '', + maxSupply: Asset.from(0, symbolPrecision), + bytes: '000000000000000000000000810090f35dfa6b18b5eb59d298e2a2443a2811e2', // EVM address } - const lockbox = { - account: 'lockbox', + const xerc20 = { + symbol: `${xsymbol}`, + account: `${xsymbol.toLowerCase()}.token`, + maxSupply: Asset.from(maxSupply, xsymbolPrecision), + minFee: Asset.from(minFee, xsymbolPrecision), contract: null, } @@ -85,37 +78,39 @@ describe('Adapter EVM -> EOS testing', () => { contract: null, } - const blockchain = new Blockchain() + const evmEA = new ProofcastEventAttestator({ + version: Versions.V1, + protocolId: Protocols.Evm, + chainId: Chains(Protocols.Evm).Mainnet, + }) - const user = 'user' - const evil = 'evil' - const issuer = 'issuer' - const bridge = 'bridge' - const recipient = 'eosrecipient' - const feemanager = 'feemanager' + const eosEA = new ProofcastEventAttestator({ + version: Versions.V1, + protocolId: Protocols.Eos, + chainId: Chains(Protocols.Eos).Mainnet, + }) - before(async () => { + before(() => { blockchain.createAccounts(user, evil, issuer, bridge, recipient, feemanager) - lockbox.contract = deploy( - blockchain, - lockbox.account, - 'contracts/build/lockbox', - ) - evmXERC20.contract = deploy( + + xerc20.contract = deploy( blockchain, - evmXERC20.account, + xerc20.account, 'contracts/build/xerc20.token', ) + adapter.contract = deploy( blockchain, adapter.account, 'contracts/build/adapter', ) + notInitAdapter.contract = deploy( blockchain, notInitAdapter.account, 'contracts/build/adapter', ) + receiver.contract = deploy( blockchain, receiver.account, @@ -123,512 +118,200 @@ describe('Adapter EVM -> EOS testing', () => { ) }) - describe('adapter::create', () => { - it('Should throw if called by not authorized account', async () => { - const action = adapter.contract.actions - .create([ - evmXERC20.account, - precision(evmXERC20.precision, evmXERC20.symbol), - evmUnderlyingToken.account, - precision(evmUnderlyingToken.precision, evmUnderlyingToken.symbol), - evmUnderlyingToken.bytes, - evmXERC20.minFee, - ]) - .send(active(evil)) - - await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) - }) + const setup = async () => { + await xerc20.contract.actions + .create([issuer, xerc20.maxSupply]) + .send(active(xerc20.account)) - // This test requires a try catch because expectToThrow do not work with error generated by passing a wrong type to an action - it('Should throw if tokenByte size is not 32', async () => { - const wrongBytes = 'aa' - try { - await adapter.contract.actions - .create([ - evmXERC20.account, - precision(evmXERC20.precision, evmXERC20.symbol), - evmUnderlyingToken.account, - precision(evmUnderlyingToken.precision, evmUnderlyingToken.symbol), - wrongBytes, - evmXERC20.minFee, - ]) - .send(active(adapter.account)) - - fail() - } catch (_err) { - expect(_err.underlyingError.toString()).to.be.equal( - `Error: Checksum size mismatch, expected 32 bytes got ${wrongBytes.length / 2}`, - ) - } - }) + const mintingLimit = Asset.from(1000, xsymbolPrecision) + const burningLimit = Asset.from(600, xsymbolPrecision) - it('Should throw if xERC20 account does not exist', async () => { - const action = adapter.contract.actions - .create([ - 'undeployed', - precision(evmXERC20.precision, evmXERC20.symbol), - evmUnderlyingToken.account, - precision(evmUnderlyingToken.precision, evmUnderlyingToken.symbol), - evmUnderlyingToken.bytes, - evmXERC20.minFee, - ]) - .send(active(adapter.account)) - - await expectToThrow(action, 'eosio_assert: xERC20 account does not exist') - }) - - it('Should throw if minFee precision does not match', async () => { - const action = adapter.contract.actions - .create([ - evmXERC20.account, - precision(evmXERC20.precision, evmXERC20.symbol), - evmUnderlyingToken.account, - precision(evmUnderlyingToken.precision, evmUnderlyingToken.symbol), - evmUnderlyingToken.bytes, - '12, XTST', - ]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.INVALID_MINFEE_SYMBOL) - }) - - it('Should throw if minFee symbol does not match', async () => { - const action = adapter.contract.actions - .create([ - evmXERC20.account, - precision(evmXERC20.precision, evmXERC20.symbol), - evmUnderlyingToken.account, - precision(evmUnderlyingToken.precision, evmUnderlyingToken.symbol), - evmUnderlyingToken.bytes, - `${adjustPrecision('0.0018', evmXERC20Precision)} XYYY`, - ]) - .send(active(adapter.account)) + await xerc20.contract.actions + .setlimits([adapter.account, mintingLimit, burningLimit]) + .send(active(xerc20.account)) + } - await expectToThrow(action, errors.INVALID_MINFEE_SYMBOL) + describe('adapter::settle', () => { + const operation = getOperation({ + blockId: + '7e21ba208ea2a072bad2d011dbc3a9f870c574a66203d84bde926fcf85756d78', + txId: '2e3704b180feda25af9dfe50793e292fd99d644aa505c3d170fa69407091dbd3', + nonce: 0, + token: '0x810090f35dfa6b18b5eb59d298e2a2443a2811e2', + originChainId: evmOriginChainId, + destinationChainId: Chains(Protocols.Eos).Mainnet, + amount: Asset.from(evmSwapAmount, xsymbolPrecision), + sender: + '000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266', + recipient, + data: '', }) - it('Should throw if xERC20 symbol is not found on xERC20 account', async () => { - const unknownSymbol = 'XXYY' - const action = adapter.contract.actions - .create([ - evmXERC20.account, - precision(evmXERC20.precision, unknownSymbol), - evmUnderlyingToken.account, - precision(evmUnderlyingToken.precision, evmUnderlyingToken.symbol), - evmUnderlyingToken.bytes, - `${adjustPrecision('0.0018', evmXERC20Precision)} ${unknownSymbol}`, - ]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.WRONG_SYM_PRECISION) - }) + const event = { + blockHash: operation.blockId, + transactionHash: operation.txId, + address: evmAdapter, + topics: [evmTopicZero], + data: serializeOperation(operation), + } + const preimage = no0x(evmEA.getEventPreImage(event)) + const signature = no0x(evmEA.formatEosSignature(evmEA.sign(event))) - it('Should throw if symbol precision is not correct', async () => { - const action = adapter.contract.actions - .create([ - evmXERC20.account, - precision(13, evmXERC20.symbol), - evmUnderlyingToken.account, - precision(evmUnderlyingToken.precision, evmUnderlyingToken.symbol), - evmUnderlyingToken.bytes, - `${adjustPrecision('0.0018', 13)} X${evmTokenSymbol}`, - ]) - .send(active(adapter.account)) + const metadata = { preimage, signature } - await expectToThrow(action, errors.WRONG_SYM_PRECISION) + it('Setup', async () => { + await setup() }) - //TODO deploy local token - // it('Should throw if token is local and token symbol is not found on xERC20 account', async () => { - // const action = adapter.contract.actions - // .create([ - // evmXERC20.account, - // precision(evmXERC20.precision, evmXERC20.symbol), - // evmUnderlyingToken.account, - // precision(evmUnderlyingToken.precision, 'XYYY'), - // evmUnderlyingToken.bytes, - // evmXERC20.minFee, - // ]) - // .send(active(adapter.account)) - - // await expectToThrow(action, 'eosio_assert: invalid minimum fee symbol') - // }) - // it('Should throw if token is local and xERC20 precision do not match it', async () => { - // const action = adapter.contract.actions - // .create([ - // evmXERC20.account, - // precision(evmXERC20.precision, evmXERC20.symbol), - // 'token', - // precision(evmUnderlyingToken.precision, evmUnderlyingToken.symbol), - // evmUnderlyingToken.bytes, - // evmXERC20.minFee, - // ]) - // .send(active(adapter.account)) - - // await expectToThrow(action, 'eosio_assert: invalid minimum fee symbol') - // }) - // it('Should throw if token is local and token account does not exist', async () => { - // const action = adapter.contract.actions - // .create([ - // 'undeployed', - // precision(evmXERC20.precision, evmXERC20.symbol), - // evmUnderlyingToken.account, - // precision(evmUnderlyingToken.precision, evmUnderlyingToken.symbol), - // evmUnderlyingToken.bytes, - // evmXERC20.minFee, - // ]) - // .send(active(adapter.account)) - - // await expectToThrow(action, 'eosio_assert: xERC20 account does not exist') - // }) - - const setup = async () => { - await evmXERC20.contract.actions - .create([issuer, evmXERC20.maxSupply]) - .send(active(evmXERC20.account)) - - const mintingLimit = `${adjustPrecision('1000', evmXERC20Precision)} ${evmXERC20.symbol}` - const burningLimit = `${adjustPrecision('600', evmXERC20Precision)} ${evmXERC20.symbol}` - - await evmXERC20.contract.actions - .setlimits([adapter.account, mintingLimit, burningLimit]) - .send(active(evmXERC20.account)) - } - it('Should create the pair successfully', async () => { - await setup() - await adapter.contract.actions .create([ - evmXERC20.account, - precision(evmXERC20.precision, evmXERC20.symbol), - evmUnderlyingToken.account, - precision(evmUnderlyingToken.precision, evmUnderlyingToken.symbol), - evmUnderlyingToken.bytes, - evmXERC20.minFee, - ]) - .send(active(adapter.account)) - - const row = getSingletonInstance(adapter.contract, 'regadapter') - const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) - const tee = getSingletonInstance(adapter.contract, 'tee') - const mappingsRow = adapter.contract.tables - .mappings(getAccountCodeRaw(adapter.account)) - .getTableRows() - - expect(row).to.be.deep.equal({ - token: '', - token_symbol: precision(18, 'XXX'), - token_bytes: evmUnderlyingToken.bytes, - xerc20: evmXERC20.account, - xerc20_symbol: precision(evmXERC20.precision, evmXERC20.symbol), - min_fee: evmXERC20.minFee, - }) - expect(storage).be.deep.equal({ - nonce: 0, - feesmanager: '', - }) - expect(tee).to.be.undefined - expect(mappingsRow).to.be.deep.equal([]) - }) - - it('Should throw if already created', async () => { - const action = adapter.contract.actions - .create([ - 'xerc20.2', - precision(6, 'XERC'), - 'local', - precision(6, 'XXX'), - evmUnderlyingToken.bytes, - evmXERC20.minFee, + xerc20.account, + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + xerc20.minFee, ]) .send(active(adapter.account)) - - await expectToThrow(action, 'eosio_assert: adapter already initialized') - }) - }) - - describe('adapter::setfeemanagr', () => { - it('Should throw if called by not authorized account', async () => { - const action = adapter.contract.actions - .setfeemanagr([feemanager]) - .send(active(evil)) - - await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) - }) - - it('Should throw if adapter is not initialized', async () => { - const action = notInitAdapter.contract.actions - .setfeemanagr([feemanager]) - .send(active(notInitAdapter.account)) - - await expectToThrow(action, errors.NOT_INITIALIZED) - }) - - it('Should set the feemanager correctly', async () => { - await adapter.contract.actions - .setfeemanagr([user]) - .send(active(adapter.account)) - - const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) - - expect(storage).be.deep.equal({ - nonce: 0, - feesmanager: user, - }) }) - it('Should update the feemanager correctly', async () => { - await adapter.contract.actions - .setfeemanagr([feemanager]) - .send(active(adapter.account)) - - const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) - - expect(storage).be.deep.equal({ - nonce: 0, - feesmanager: feemanager, - }) - }) - }) - - describe('adapter::settee', () => { - it('Should throw if called by not authorized account', async () => { - const action = adapter.contract.actions - .settee([teePubKey, attestation]) - .send(active(evil)) - - await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) - }) - - it('Should set the tee pubKey and attestation correctly', async () => { + it('Should add the tee public key successfully', async () => { + const teePubKey = fromEthersPublicKey( + evmEA.signingKey.compressedPublicKey, + ) + const attestation = '' await adapter.contract.actions .settee([teePubKey, attestation]) .send(active(adapter.account)) - - const tee = getSingletonInstance(adapter.contract, 'tee') - - expect(tee.key).to.be.equal(teePubKey.toString()) - expect(tee.attestation).to.be.equal(attestation) - }) - }) - - describe('adapter::setorigin', () => { - it('Should throw if called by not authorized account', async () => { - const originChainId = evmOperationSamples.pegin.originChainId - const action = adapter.contract.actions - .setorigin([ - hexStringToBytes(originChainId), - hexStringToBytes(evmAdapter), - hexStringToBytes(evmTopicZero), - ]) - .send(active(evil)) - - await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) - }) - - it('Should throw if emitter is not 32 bytes', async () => { - const originChainId = evmOperationSamples.pegin.originChainId - const wrong = '0xC0FFEE' - const action = adapter.contract.actions - .setorigin([ - hexStringToBytes(originChainId), - hexStringToBytes(wrong), - hexStringToBytes(evmTopicZero), - ]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.EXPECTED_32_BYTES('emitter')) }) - it('Should throw if topic zero is not 32 bytes', async () => { - const originChainId = evmOperationSamples.pegin.originChainId - const wrong = '0xC0FFEE' - const action = adapter.contract.actions - .setorigin([ - hexStringToBytes(originChainId), - hexStringToBytes(evmAdapter), - hexStringToBytes(wrong), - ]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.EXPECTED_32_BYTES('topic zero')) - }) - - it('Should throw if origin chain id is not 32 bytes', async () => { - const wrong = 1 - const action = adapter.contract.actions - .setorigin([ - wrong, - hexStringToBytes(evmAdapter), - hexStringToBytes(evmTopicZero), - ]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.EXPECTED_32_BYTES('chain_id')) - }) - - it('Should set the origin details correctly', async () => { - const originChainId = evmOperationSamples.pegin.originChainId - const anAddress = no0x( - ethers.zeroPadValue('0xe396757ec7e6ac7c8e5abe7285dde47b98f22db8', 32), - ) + it('Should add the origin details successfully', async () => { await adapter.contract.actions - .setorigin([ - hexStringToBytes(originChainId), - hexStringToBytes(anAddress), - hexStringToBytes(evmTopicZero), - ]) + .setorigin([no0x(bytes32(evmOriginChainId)), evmAdapter, evmTopicZero]) .send(active(adapter.account)) - - const row = adapter.contract.tables - .mappings(getAccountCodeRaw(adapter.account)) - .getTableRow(ethers.stripZerosLeft(`0x${originChainId}`)) - - expect(row.chain_id).to.be.equal(originChainId) - expect(row.emitter).to.be.equal(anAddress) - expect(row.topic_zero).to.be.equal(evmTopicZero) }) - it('Should update the origin details correctly', async () => { - const originChainId = evmOperationSamples.pegin.originChainId + it('Should add the fee manager account', async () => { await adapter.contract.actions - .setorigin([ - hexStringToBytes(originChainId), - hexStringToBytes(evmAdapter), - hexStringToBytes(evmTopicZero), - ]) + .setfeemanagr([feemanager]) .send(active(adapter.account)) - - const emitterRow = adapter.contract.tables - .mappings(getAccountCodeRaw(adapter.account)) - .getTableRow(ethers.stripZerosLeft(`0x${originChainId}`)) - - expect(emitterRow.chain_id).to.be.equal(originChainId) - expect(emitterRow.emitter).to.be.equal(evmAdapter) - expect(emitterRow.topic_zero).to.be.equal(evmTopicZero) }) - }) - - describe('adapter::swap', () => { - it('Should swap correctly', async () => { - // TODO - }) - }) - - describe('adapter::settle', () => { - const createOperationAsset = amount => - Asset.from( - amount / 10 ** evmPrecision, - precision(evmXERC20.precision, evmXERC20.symbol), - ) it('Should reject if adapter and token do not match', async () => { - const operation = evmOperationSamples.pegin - const metadata = evmMetadataSamples.pegin - + const wrongTokenBytes = no0x(bytes32('0x')) const action = adapter.contract.actions - .settle([user, { ...operation, token: wrongToken.bytes }, metadata]) + .settle([ + user, + { ...no0x(operation), token: wrongTokenBytes }, + metadata, + ]) .send(active(user)) await expectToThrow(action, errors.INVALID_TOKEN) }) it('Should settle the operation properly', async () => { - const operation = evmOperationSamples.pegin - const metadata = evmMetadataSamples.pegin - const before = getAccountsBalances( [user, recipient, adapter.account, feemanager], - [evmXERC20], + [xerc20], ) await adapter.contract.actions - .settle([user, operation, metadata]) + .settle([user, no0x(operation), metadata]) .send(active(user)) const after = getAccountsBalances( [user, recipient, adapter.account, feemanager], - [evmXERC20], + [xerc20], ) - const operationAsset = createOperationAsset(operation.amount) - expect(after[recipient][evmXERC20.symbol]).to.be.equal( - operationAsset.toString(), + expect(after[recipient][xerc20.symbol]).to.be.equal( + operation.amount.toString(), ) - expect(after[adapter.account][evmXERC20.symbol]).to.be.equal( - `0.0000 ${evmXERC20.symbol}`, + expect(after[adapter.account][xerc20.symbol]).to.be.equal( + `0.0000 ${xerc20.symbol}`, ) - expect(before[feemanager][evmXERC20.symbol]).to.be.equal( - after[feemanager][evmXERC20.symbol], + expect(before[feemanager][xerc20.symbol]).to.be.equal( + after[feemanager][xerc20.symbol], ) }) it('Should settle the operation properly and send userdata', async () => { - const operation = evmOperationSamples.peginWithUserData - const metadata = evmMetadataSamples.peginWithUserData + const operationWithUserData = { + ...operation, + data: '0x12345abcdefc0de1337f', + } + + const newEvent = { + ...event, + data: serializeOperation(operationWithUserData), + } + + const newMetadata = { + preimage: evmEA.getEventPreImage(newEvent), + signature: evmEA.formatEosSignature(evmEA.sign(newEvent)), + } const before = getAccountsBalances( [user, recipient, adapter.account], - [evmXERC20], + [xerc20], ) - const beforeAsset = Asset.from(before[recipient][evmXERC20.symbol]) + + const beforeAsset = Asset.from(before[recipient][xerc20.symbol]) await adapter.contract.actions - .settle([user, operation, metadata]) + .settle([user, no0x(operationWithUserData), no0x(newMetadata)]) .send(active(user)) const after = getAccountsBalances( [user, recipient, adapter.account], - [evmXERC20], + [xerc20], ) - const operationAsset = createOperationAsset(operation.amount) - expect(after[recipient][evmXERC20.symbol]).to.equal( - sum(operationAsset, beforeAsset).toString(), + expect(after[recipient][xerc20.symbol]).to.equal( + sum(operation.amount, beforeAsset).toString(), ) - expect(after[adapter.account][evmXERC20.symbol]).to.be.equal( - `0.0000 ${evmXERC20.symbol}`, + expect(after[adapter.account][xerc20.symbol]).to.be.equal( + `0.0000 ${xerc20.symbol}`, ) }) + }) - it('Should truncate the passed amount to the set precision', async () => { - const operation = evmOperationSamples.peginLargePrecision - const metadata = evmMetadataSamples.peginLargePrecision - - const before = getAccountsBalances( - [user, recipient, adapter.account], - [evmXERC20], - ) - const beforeAsset = Asset.from(before[recipient][evmXERC20.symbol]) - - await adapter.contract.actions - .settle([user, operation, metadata]) - .send(active(user)) - - console.log(adapter.contract.bc.console) - - const after = getAccountsBalances( - [user, recipient, adapter.account], - [evmXERC20], + describe('adapter::swap', () => { + it('Should swap the amount back to the originating chain', async () => { + const to = '0xe396757ec7e6ac7c8e5abe7285dde47b98f22db8' + const destinationChainId = bytes32(Chains(Protocols.Evm).Mainnet) + const data = '' + const memo = getSwapMemo(user, destinationChainId, to, data) + const quantity = Asset.from(evmSwapAmount, xsymbolPrecision) + + const before = getAccountsBalances([recipient, adapter.account], [xerc20]) + + await xerc20.contract.actions + .transfer([recipient, adapter.account, quantity, memo]) + .send(active(recipient)) + + const after = getAccountsBalances([recipient, adapter.account], [xerc20]) + + expect( + substract( + before[recipient][xerc20.symbol], + after[recipient][xerc20.symbol], + ), + ).to.be.deep.equal(quantity) + + expect(after[adapter.account][xerc20.symbol]).to.be.deep.equal( + Asset.from(0, xsymbolPrecision).toString(), ) - const operationAsset = createOperationAsset(operation.amount) - const adjAmount = adjustPrecision( - operationAsset.toString(), - evmXERC20Precision, - ) - const adjOperationAmount = Asset.from(`${adjAmount} ${evmXERC20.symbol}`) - expect(after[recipient][evmXERC20.symbol]).to.equal( - sum(adjOperationAmount, beforeAsset).toString(), - ) + const expectedEventBytes = + '0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000005158b1b8444260000000000000000000000000000000000000000000000000000000000075736572000000000000000000000000000000000000000000000000000000000000002a307865333936373537656337653661633763386535616265373238356464653437623938663232646238' - expect(after[adapter.account][evmXERC20.symbol]).to.be.equal( - `0.0000 ${evmXERC20.symbol}`, - ) + expect(adapter.contract.bc.console).to.be.equal(expectedEventBytes) }) }) }) From c980f7f8479dc1842c15109394c5b6c3c2e05024 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Thu, 7 Nov 2024 16:02:02 +0100 Subject: [PATCH 02/23] chore(adapter.test.js): aggregate tests into a single suite --- cpp/contracts/adapter.cpp | 4 +- cpp/test/adapter.test.js | 504 +++++++++++++++++++++++++++++++++ cpp/test/utils/eos-ext.js | 6 +- cpp/test/utils/errors.js | 8 +- cpp/test/utils/wharfkit-ext.js | 10 +- 5 files changed, 526 insertions(+), 6 deletions(-) create mode 100644 cpp/test/adapter.test.js diff --git a/cpp/contracts/adapter.cpp b/cpp/contracts/adapter.cpp index 15f16e8..a497999 100644 --- a/cpp/contracts/adapter.cpp +++ b/cpp/contracts/adapter.cpp @@ -8,7 +8,7 @@ asset adapter::calculate_fees(const asset& quantity) { auto registry_data = _registry.get(); check( - quantity.symbol == registry_data.token_symbol || + quantity.symbol == registry_data.token_symbol || quantity.symbol == registry_data.xerc20_symbol, "invalid quantity given for calculating the fees" ); @@ -27,6 +27,7 @@ void adapter::check_symbol_is_valid(const name& account, const symbol& sym) { stats _stats(account, sym.code().raw()); auto itr = _stats.find(sym.code().raw()); check(itr != _stats.end(), "symbol not found"); + check(sym == itr->supply.symbol, "invalid symbol"); } void adapter::create( @@ -45,6 +46,7 @@ void adapter::create( check(is_account(xerc20), "xERC20 account does not exist"); check(min_fee.symbol == xerc20_symbol, "invalid minimum fee symbol"); check_symbol_is_valid(xerc20, xerc20_symbol); + check(min_fee.symbol == xerc20_symbol, "invalid minimum fee symbol"); // Checks done only for the local token if (token != name(0)) { diff --git a/cpp/test/adapter.test.js b/cpp/test/adapter.test.js new file mode 100644 index 0000000..8359e08 --- /dev/null +++ b/cpp/test/adapter.test.js @@ -0,0 +1,504 @@ +const { expect } = require('chai') +const { Blockchain, expectToThrow } = require('@eosnetwork/vert') +const { Asset } = require('@wharfkit/antelope') +const { Symbol } = Asset +const { + no0x, + active, + deploy, + errors, + getSymbolCodeRaw, + getAccountCodeRaw, + fromEthersPublicKey, + getSingletonInstance, +} = require('./utils') +const { toBeHex, zeroPadValue, stripZerosLeft } = require('ethers') +const { + Chains, + Versions, + Protocols, + ProofcastEventAttestator, +} = require('@pnetwork/event-attestator') + +const LOCAL = 'Local' +const NOT_LOCAL = 'Not-local' +const TABLE_STORAGE = 'storage' + +describe('Adapter tests', () => { + const symbol = 'TST' + const xsymbol = `X${symbol}` + const maxSupply = 500000000 + const minFee = 0.0018 + const precision = 8 + const symbolPrecision = Symbol.fromParts(symbol, precision) + const xsymbolPrecision = Symbol.fromParts(xsymbol, precision) + + const blockchain = new Blockchain() + + const user = 'user' + const evil = 'evil' + const issuer = 'issuer' + const bridge = 'bridge' + const recipient = 'eosrecipient' + const feemanager = 'feemanager' + + const notLocalToken = { + symbol, + account: '', + maxSupply: Asset.from(0, symbolPrecision), + bytes: '000000000000000000000000810090f35dfa6b18b5eb59d298e2a2443a2811e2', // EVM address + } + + const tokenAccount = `${symbol.toLowerCase()}.token` + const localToken = { + symbol, + account: tokenAccount, + maxSupply: Asset.from(maxSupply, symbolPrecision), + bytes: no0x( + zeroPadValue(toBeHex(String(getSymbolCodeRaw(symbolPrecision))), 32), + ), + } + + const xerc20 = { + symbol: `${xsymbol}`, + account: `${xsymbol.toLowerCase()}.token`, + maxSupply: Asset.from(maxSupply, xsymbolPrecision), + minFee: Asset.from(minFee, xsymbolPrecision), + contract: null, + } + + const lockbox = { + account: 'lockbox', + contract: null, + } + + const adapter = { + account: 'adapter', + contract: null, + } + + const notInitAdapter = { + account: 'adapter2', + contract: null, + } + + const receiver = { + account: 'receiver', + contract: null, + } + + const evmEA = new ProofcastEventAttestator({ + version: Versions.V1, + protocolId: Protocols.Evm, + chainId: Chains(Protocols.Evm).Mainnet, + }) + + const eosEA = new ProofcastEventAttestator({ + version: Versions.V1, + protocolId: Protocols.Eos, + chainId: Chains(Protocols.Eos).Mainnet, + }) + + ;[NOT_LOCAL, LOCAL].map(_type => { + describe(_type, () => { + before(() => { + blockchain.createAccounts( + user, + evil, + issuer, + bridge, + recipient, + feemanager, + ) + + xerc20.contract = deploy( + blockchain, + xerc20.account, + 'contracts/build/xerc20.token', + ) + + adapter.contract = deploy( + blockchain, + adapter.account, + 'contracts/build/adapter', + ) + + notInitAdapter.contract = deploy( + blockchain, + notInitAdapter.account, + 'contracts/build/adapter', + ) + + receiver.contract = deploy( + blockchain, + receiver.account, + 'contracts/build/test.receiver', + ) + + if (_type == LOCAL) { + lockbox.contract = deploy( + blockchain, + lockbox.account, + 'contracts/build/lockbox', + ) + + localToken.contract = deploy( + blockchain, + localToken.account, + 'contracts/build/eosio.token', + ) + } + }) + + after(async () => { + blockchain.resetTables() + await blockchain.resetVm() + }) + + const token = _type === LOCAL ? localToken : notLocalToken + + describe('adapter::create', () => { + it('Should throw if called by not authorized account', async () => { + const action = adapter.contract.actions + .create([ + xerc20.account, + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + xerc20.minFee, + ]) + .send(active(evil)) + + await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) + }) + + it('Should throw if tokenByte size is not 32', async () => { + const wrongBytes = 'aa' + try { + await adapter.contract.actions + .create([ + xerc20.account, + xsymbolPrecision, + token.account, + symbolPrecision, + wrongBytes, + xerc20.minFee, + ]) + .send(active(adapter.account)) + + fail() + } catch (_err) { + expect(_err.underlyingError.toString()).to.be.equal( + `Error: Checksum size mismatch, expected 32 bytes got ${wrongBytes.length / 2}`, + ) + } + }) + + it('Should throw if xERC20 account does not exist', async () => { + const wrongAccount = 'undeployed' + const action = adapter.contract.actions + .create([ + wrongAccount, + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + xerc20.minFee, + ]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.INVALID_ACCOUNT) + }) + + const deployTokenContracts = async () => { + await xerc20.contract.actions + .create([issuer, xerc20.maxSupply]) + .send(active(xerc20.account)) + + const mintingLimit = Asset.from(1000, xsymbolPrecision) + const burningLimit = Asset.from(600, xsymbolPrecision) + + await xerc20.contract.actions + .setlimits([adapter.account, mintingLimit, burningLimit]) + .send(active(xerc20.account)) + + if (_type === LOCAL) { + await localToken.contract.actions + .create([issuer, localToken.maxSupply]) + .send(active(localToken.account)) + } + } + + it('Should throw if minFee precision does not match', async () => { + await deployTokenContracts() + + const wrongPrecision = precision - 1 + const wrongSymbol = Symbol.fromParts(xsymbol, wrongPrecision) + const wrongMinFee = Asset.from(minFee, wrongSymbol) + const action = adapter.contract.actions + .create([ + xerc20.account, + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + wrongMinFee, + ]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.INVALID_MINFEE_SYMBOL) + }) + + it('Should throw if minFee symbol does not match', async () => { + const wrongSymbol = Symbol.fromParts('WRONG', precision) + const wrongMinFee = Asset.from(minFee, wrongSymbol) + const action = adapter.contract.actions + .create([ + xerc20.account, + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + wrongMinFee, + ]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.INVALID_MINFEE_SYMBOL) + }) + + it('Should throw if xERC20 symbol is not found on xERC20 account', async () => { + const unknownSymbol = 'XXYY' + const wrongXERC20 = Symbol.fromParts(unknownSymbol, precision) + + const action = adapter.contract.actions + .create([ + xerc20.account, + wrongXERC20, + token.account, + symbolPrecision, + token.bytes, + xerc20.minFee, + ]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.SYMBOL_NOT_FOUND) + }) + + it('Should throw if symbol precision is not correct', async () => { + const wrongPrecision = precision - 1 + const wrongSymbol = Symbol.fromParts(xsymbol, wrongPrecision) + const action = adapter.contract.actions + .create([ + xerc20.account, + wrongSymbol, + token.account, + symbolPrecision, + token.bytes, + Asset.from(10, wrongSymbol), + ]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.INVALID_SYMBOL) + }) + + it('Should create the pair successfully', async () => { + await adapter.contract.actions + .create([ + xerc20.account, + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + xerc20.minFee, + ]) + .send(active(adapter.account)) + + const row = adapter.contract.tables + .regadapter(getAccountCodeRaw(adapter.account)) + .getTableRow(getSymbolCodeRaw(token.maxSupply)) + const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) + const tee = getSingletonInstance(adapter.contract, 'tee') + const mappingsRow = adapter.contract.tables + .mappings(getAccountCodeRaw(adapter.account)) + .getTableRows() + + expect(row).to.be.deep.equal({ + token: token.account, + token_symbol: symbolPrecision.toString(), + token_bytes: token.bytes, + xerc20: xerc20.account, + xerc20_symbol: xsymbolPrecision.toString(), + min_fee: xerc20.minFee.toString(), + }) + expect(storage).be.deep.equal({ + nonce: 0, + feesmanager: '', + }) + expect(tee).to.be.undefined + expect(mappingsRow).to.be.deep.equal([]) + }) + + it('Should throw if already created', async () => { + const action = adapter.contract.actions + .create([ + xerc20.account, + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + xerc20.minFee, + ]) + .send(active(adapter.account)) + + await expectToThrow(action, 'eosio_assert: token already registered') + }) + }) + + describe('adapter::setfeemanagr', () => { + it('Should throw if called by not authorized account', async () => { + const action = adapter.contract.actions + .setfeemanagr([feemanager]) + .send(active(evil)) + + await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) + }) + + it('Should throw if adapter is not initialized', async () => { + const action = notInitAdapter.contract.actions + .setfeemanagr([feemanager]) + .send(active(notInitAdapter.account)) + + await expectToThrow(action, errors.NOT_INITIALIZED) + }) + + it('Should set the feemanager correctly', async () => { + await adapter.contract.actions + .setfeemanagr([user]) + .send(active(adapter.account)) + + const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) + + expect(storage).be.deep.equal({ + nonce: 0, + feesmanager: user, + }) + }) + + it('Should update the feemanager correctly', async () => { + await adapter.contract.actions + .setfeemanagr([feemanager]) + .send(active(adapter.account)) + + const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) + + expect(storage).be.deep.equal({ + nonce: 0, + feesmanager: feemanager, + }) + }) + }) + + describe('adapter::settee', () => { + const teePubKey = fromEthersPublicKey( + evmEA.signingKey.compressedPublicKey, + ) + const attestation = '' + it('Should throw if called by not authorized account', async () => { + const action = adapter.contract.actions + .settee([teePubKey, attestation]) + .send(active(evil)) + + await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) + }) + + it('Should set the tee pubKey and attestation correctly', async () => { + await adapter.contract.actions + .settee([teePubKey, attestation]) + .send(active(adapter.account)) + + const tee = getSingletonInstance(adapter.contract, 'tee') + + expect(tee.key).to.be.equal(teePubKey.toString()) + expect(tee.attestation).to.be.equal(attestation) + }) + }) + + describe('adapter::setorigin', () => { + const originChainId = no0x( + zeroPadValue(Chains(Protocols.Evm).Mainnet, 32), + ) + const evmAdapter = + '000000000000000000000000bcf063a9eb18bc3c6eb005791c61801b7cb16fe4' + const evmTopicZero = + '66756e6473206172652073616675207361667520736166752073616675202e2e' + + it('Should throw if called by not authorized account', async () => { + const action = adapter.contract.actions + .setorigin([originChainId, evmAdapter, evmTopicZero]) + .send(active(evil)) + + await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) + }) + + it('Should throw if emitter is not 32 bytes', async () => { + const wrong = 'C0FFEE' + const action = adapter.contract.actions + .setorigin([originChainId, wrong, evmTopicZero]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.EXPECTED_32_BYTES('emitter')) + }) + + it('Should throw if topic zero is not 32 bytes', async () => { + const wrong = 'C0FFEE' + const action = adapter.contract.actions + .setorigin([originChainId, evmAdapter, wrong]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.EXPECTED_32_BYTES('topic zero')) + }) + + it('Should throw if origin chain id is not 32 bytes', async () => { + const wrong = 1 + const action = adapter.contract.actions + .setorigin([wrong, evmAdapter, evmTopicZero]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.EXPECTED_32_BYTES('chain_id')) + }) + + it('Should set the origin details correctly', async () => { + const anAddress = no0x( + zeroPadValue('0xe396757ec7e6ac7c8e5abe7285dde47b98f22db8', 32), + ) + await adapter.contract.actions + .setorigin([originChainId, anAddress, evmTopicZero]) + .send(active(adapter.account)) + + const row = adapter.contract.tables + .mappings(getAccountCodeRaw(adapter.account)) + .getTableRow(stripZerosLeft(`0x${originChainId}`)) + + expect(row.chain_id).to.be.equal(originChainId) + expect(row.emitter).to.be.equal(anAddress) + expect(row.topic_zero).to.be.equal(evmTopicZero) + }) + + it('Should update the origin details correctly', async () => { + await adapter.contract.actions + .setorigin([originChainId, evmAdapter, evmTopicZero]) + .send(active(adapter.account)) + + const emitterRow = adapter.contract.tables + .mappings(getAccountCodeRaw(adapter.account)) + .getTableRow(stripZerosLeft(`0x${originChainId}`)) + + expect(emitterRow.chain_id).to.be.equal(originChainId) + expect(emitterRow.emitter).to.be.equal(evmAdapter) + expect(emitterRow.topic_zero).to.be.equal(evmTopicZero) + }) + }) + }) + }) +}) diff --git a/cpp/test/utils/eos-ext.js b/cpp/test/utils/eos-ext.js index 8482e7e..f231ecc 100644 --- a/cpp/test/utils/eos-ext.js +++ b/cpp/test/utils/eos-ext.js @@ -1,11 +1,15 @@ const R = require('ramda') const { Asset, Name } = require('@wharfkit/antelope') +const { Symbol } = Asset const active = _account => `${_account}@active` // TODO: replace w/ symbolCodeToBigInt -const getSymbolCodeRaw = _asset => Asset.from(_asset).symbol.code.value.value +const getSymbolCodeRaw = _assetOrSymbol => + _assetOrSymbol instanceof Asset + ? Asset.from(_assetOrSymbol).symbol.code.value.value + : Symbol.from(_assetOrSymbol).value.value // TODO: replace w/ nameToBigInt const getAccountCodeRaw = _account => Name.from(_account).value.value diff --git a/cpp/test/utils/errors.js b/cpp/test/utils/errors.js index d83e740..5073ae1 100644 --- a/cpp/test/utils/errors.js +++ b/cpp/test/utils/errors.js @@ -11,6 +11,7 @@ const INSUFFICIENT_BALANCE_INC = eosio_assert( const NO_ALLOWANCE_SET = eosio_assert('No allowance set for this node') const AUTH_MISSING = _account => `missing required authority ${_account}` + const SYMBOL_NOT_FOUND = eosio_assert('symbol not found') const FROM_ACCOUNT_IS_FROZEN = eosio_assert('from account is frozen') @@ -26,8 +27,6 @@ const INVALID_MINFEE_SYMBOL = eosio_assert('invalid minimum fee symbol') const NOT_INITIALIZED = eosio_assert('adapter contract not initialized') -const WRONG_SYM_PRECISION = eosio_assert('symbol not found') - const SINGLETON_NOT_EXISTING = eosio_assert('singleton does not exist') const ORIGIN_CHAINID_NOT_REGISTERED = eosio_assert( @@ -62,6 +61,8 @@ const ACCOUNT_STR_IS_TOO_LONG = eosio_assert( const INVALID_ACCOUNT = eosio_assert('invalid account') +const INVALID_SYMBOL = eosio_assert('invalid symbol') + const EXPECTED_32_BYTES = _thing => eosio_assert(`expected 32 bytes ${_thing}`) module.exports = { @@ -79,7 +80,7 @@ module.exports = { INVALID_SIGNATURE, INVALID_MINFEE_SYMBOL, NOT_INITIALIZED, - WRONG_SYM_PRECISION, + SYMBOL_NOT_FOUND, SINGLETON_NOT_EXISTING, ORIGIN_CHAINID_NOT_REGISTERED, INVALID_SIGNATURE, @@ -95,4 +96,5 @@ module.exports = { INVALID_ACCOUNT, ACCOUNT_STR_IS_TOO_LONG, EXPECTED_32_BYTES, + INVALID_SYMBOL, } diff --git a/cpp/test/utils/wharfkit-ext.js b/cpp/test/utils/wharfkit-ext.js index 800b665..96114fd 100644 --- a/cpp/test/utils/wharfkit-ext.js +++ b/cpp/test/utils/wharfkit-ext.js @@ -1,6 +1,7 @@ const R = require('ramda') -const { Asset } = require('@wharfkit/antelope') const assert = require('assert') +const { no0x } = require('./bytes-utils') +const { Asset, PublicKey } = require('@wharfkit/antelope') const assetOperation = R.curry((_fn, _op1, _op2) => { const op1 = _op1 instanceof Asset ? _op1 : Asset.from(_op1) @@ -27,10 +28,17 @@ const divide = assetOperation(R.divide) const utf8HexString = _str => '0x' + Buffer.from(_str, 'utf-8').toString('hex') +const fromEthersPublicKey = _compressed => + PublicKey.from({ + type: 'K1', + compressed: Uint8Array.from(Buffer.from(no0x(_compressed), 'hex')), + }) + module.exports = { sum, divide, multiply, substract, utf8HexString, + fromEthersPublicKey, } From b307f826aa45fb81da4f2cbedd269fcbd4e1acb8 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 11:55:29 +0100 Subject: [PATCH 03/23] chore(bytes-utils.js): improve `no0x` & `_0x` --- cpp/test/utils/bytes-utils.js | 38 ++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/cpp/test/utils/bytes-utils.js b/cpp/test/utils/bytes-utils.js index 09bc23e..b33a3cf 100644 --- a/cpp/test/utils/bytes-utils.js +++ b/cpp/test/utils/bytes-utils.js @@ -1,3 +1,6 @@ +const R = require('ramda') +const { zeroPadValue } = require('ethers') + const getXbytesHex = (hex, offset, byteNum) => hex.slice(offset * 2, offset * 2 + byteNum * 2) @@ -9,14 +12,47 @@ const hexToString = hex => { return str } -const no0x = _0xValue => _0xValue.replace('0x', '') +const replaceOrIdentity = (_regExp, _value, _x) => { + try { + return R.replace(_regExp, _value, _x) + } catch (e) { + return _x + } +} + +// If input is an object, then +// { k: '0x1111' } +// +// becomes +// +// { k: '1111'} +// +const no0x = _0xValue => + R.type(_0xValue) === 'Object' + ? R.keys(_0xValue).reduce( + (acc, elem) => ({ + ...acc, + [elem]: replaceOrIdentity('0x', '', _0xValue[elem]), + }), + _0xValue, + ) + : _0xValue.replace('0x', '') + +const _0x = _value => + _value instanceof Buffer + ? `0x${no0x(_value.toString('hex'))}` + : `0x${no0x(_value)}` const hexStringToBytes = _hex => Uint8Array.from(Buffer.from(no0x(_hex), 'hex')) const removeNullChars = string => string.replace(/\u0000/g, '') +const bytes32 = _value => zeroPadValue(_0x(_value), 32) + module.exports = { + _0x, no0x, + bytes32, getXbytesHex, hexToString, hexStringToBytes, From 17634616f77fe66ca7174c964cc072c2e215d2c6 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 11:56:29 +0100 Subject: [PATCH 04/23] chore(get-operation-sample.js): add serialization + automate Operation creation --- cpp/test/utils/get-operation-sample.js | 70 ++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/cpp/test/utils/get-operation-sample.js b/cpp/test/utils/get-operation-sample.js index 05fc0f5..c8a4a11 100644 --- a/cpp/test/utils/get-operation-sample.js +++ b/cpp/test/utils/get-operation-sample.js @@ -1,7 +1,9 @@ const R = require('ramda') -const { toBeHex, zeroPadValue, parseUnits } = require('ethers') - -const { no0x } = require('./bytes-utils') +const { toBeHex, concat, stripZerosLeft } = require('ethers') +const { Asset } = require('@wharfkit/antelope') +const { _0x, no0x, bytes32 } = require('./bytes-utils') +const { Protocols, Chains } = require('@pnetwork/event-attestator') +const { getSymbolCodeRaw } = require('./eos-ext') const getOperationSample = _injectedOperation => R.mergeDeepLeft(_injectedOperation, { @@ -20,6 +22,68 @@ const getOperationSample = _injectedOperation => data: '', }) +const isEosChain = _chainId => + R.values(Chains(Protocols.Eos)).includes(stripZerosLeft(_chainId)) + +const getOperation = _obj => { + const blockId = bytes32(_0x(_obj.blockId)) + const txId = bytes32(_0x(_obj.txId)) + const nonce = _obj.nonce + const originChainId = bytes32(_0x(_obj.originChainId)) + const token = isEosChain(_obj.originChainId) + ? bytes32(toBeHex(getSymbolCodeRaw(_obj.token))) + : bytes32(_0x(_obj.token)) + const destinationChainId = bytes32(_0x(_obj.destinationChainId)) + + const amount = isEosChain(_obj.destinationChainId) + ? Asset.from(_obj.amount) + : amount + + const sender = isEosChain(_obj.originChainId) + ? bytes32(_0x(Buffer.from(_obj.sender, 'utf-8').toString('hex'))) + : bytes32(_obj.sender) + const recipient = _obj.recipient + const data = _0x(_obj.data) + + return { + blockId, + txId, + nonce, + token, + originChainId, + destinationChainId, + amount, + sender, + recipient, + data, + } +} + +const serializeOperation = _operation => { + const amount = isEosChain(_operation.destinationChainId) + ? bytes32(toBeHex(BigInt(_operation.amount.value * 1e18))) // we need to include precision here + : bytes32(toBeHex(BigInt(_operation.amount))) // we expect amount to be already normalized + + const recipientLen = bytes32( + toBeHex(BigInt(no0x(_operation.recipient).length)), + ) + + const recipient = _0x(Buffer.from(_operation.recipient, 'utf-8')) + + return concat([ + bytes32(toBeHex(BigInt(_operation.nonce))), + _operation.token, + _operation.destinationChainId, + amount, + _operation.sender, + recipientLen, + recipient, + _operation.data, + ]) +} + module.exports = { + getOperation, getOperationSample, + serializeOperation, } From a69393a334359c18a21560b2676e689ea3292e87 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 11:57:08 +0100 Subject: [PATCH 05/23] chore(adapter.test.js): rm samples dependency --- cpp/test/adapter.test.js | 139 +++++++++++++++++++++++++++++++++++---- 1 file changed, 126 insertions(+), 13 deletions(-) diff --git a/cpp/test/adapter.test.js b/cpp/test/adapter.test.js index 8359e08..0ed9164 100644 --- a/cpp/test/adapter.test.js +++ b/cpp/test/adapter.test.js @@ -3,16 +3,21 @@ const { Blockchain, expectToThrow } = require('@eosnetwork/vert') const { Asset } = require('@wharfkit/antelope') const { Symbol } = Asset const { + sum, no0x, active, deploy, errors, + bytes32, + getOperation, getSymbolCodeRaw, getAccountCodeRaw, + serializeOperation, + getAccountsBalances, fromEthersPublicKey, getSingletonInstance, } = require('./utils') -const { toBeHex, zeroPadValue, stripZerosLeft } = require('ethers') +const { toBeHex, stripZerosLeft } = require('ethers') const { Chains, Versions, @@ -42,6 +47,11 @@ describe('Adapter tests', () => { const recipient = 'eosrecipient' const feemanager = 'feemanager' + const evmAdapter = + '000000000000000000000000bcf063a9eb18bc3c6eb005791c61801b7cb16fe4' + const evmTopicZero = + '66756e6473206172652073616675207361667520736166752073616675202e2e' + const notLocalToken = { symbol, account: '', @@ -54,9 +64,7 @@ describe('Adapter tests', () => { symbol, account: tokenAccount, maxSupply: Asset.from(maxSupply, symbolPrecision), - bytes: no0x( - zeroPadValue(toBeHex(String(getSymbolCodeRaw(symbolPrecision))), 32), - ), + bytes: no0x(bytes32(toBeHex(String(getSymbolCodeRaw(symbolPrecision))))), } const xerc20 = { @@ -99,7 +107,7 @@ describe('Adapter tests', () => { chainId: Chains(Protocols.Eos).Mainnet, }) - ;[NOT_LOCAL, LOCAL].map(_type => { + ;[NOT_LOCAL].map(_type => { describe(_type, () => { before(() => { blockchain.createAccounts( @@ -425,13 +433,7 @@ describe('Adapter tests', () => { }) describe('adapter::setorigin', () => { - const originChainId = no0x( - zeroPadValue(Chains(Protocols.Evm).Mainnet, 32), - ) - const evmAdapter = - '000000000000000000000000bcf063a9eb18bc3c6eb005791c61801b7cb16fe4' - const evmTopicZero = - '66756e6473206172652073616675207361667520736166752073616675202e2e' + const originChainId = no0x(bytes32(Chains(Protocols.Evm).Mainnet)) it('Should throw if called by not authorized account', async () => { const action = adapter.contract.actions @@ -470,7 +472,7 @@ describe('Adapter tests', () => { it('Should set the origin details correctly', async () => { const anAddress = no0x( - zeroPadValue('0xe396757ec7e6ac7c8e5abe7285dde47b98f22db8', 32), + bytes32('0xe396757ec7e6ac7c8e5abe7285dde47b98f22db8'), ) await adapter.contract.actions .setorigin([originChainId, anAddress, evmTopicZero]) @@ -499,6 +501,117 @@ describe('Adapter tests', () => { expect(emitterRow.topic_zero).to.be.equal(evmTopicZero) }) }) + + describe('adapter::settle', () => { + const operation = getOperation({ + blockId: + '7e21ba208ea2a072bad2d011dbc3a9f870c574a66203d84bde926fcf85756d78', + txId: '2e3704b180feda25af9dfe50793e292fd99d644aa505c3d170fa69407091dbd3', + nonce: 0, + token: '0x810090f35dfa6b18b5eb59d298e2a2443a2811e2', + originChainId: Chains(Protocols.Evm).Mainnet, // EVM mainnet chain id + destinationChainId: Chains(Protocols.Eos).Mainnet, + amount: Asset.from(5.87190615, xsymbolPrecision), + sender: + '000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266', + recipient, + data: '', + }) + + const event = { + blockHash: operation.blockId, + transactionHash: operation.txId, + address: evmAdapter, + topics: [evmTopicZero], + data: serializeOperation(operation), + } + const preimage = no0x(evmEA.getEventPreImage(event)) + const signature = no0x(evmEA.formatEosSignature(evmEA.sign(event))) + + const metadata = { preimage, signature } + + it('Should reject if adapter and token do not match', async () => { + const wrongTokenBytes = no0x(bytes32('0x')) + const action = adapter.contract.actions + .settle([ + user, + { ...no0x(operation), token: wrongTokenBytes }, + metadata, + ]) + .send(active(user)) + + await expectToThrow(action, errors.INVALID_TOKEN) + }) + + it('Should settle the operation properly', async () => { + const before = getAccountsBalances( + [user, recipient, adapter.account, feemanager], + [xerc20], + ) + + await adapter.contract.actions + .settle([user, no0x(operation), metadata]) + .send(active(user)) + + const after = getAccountsBalances( + [user, recipient, adapter.account, feemanager], + [xerc20], + ) + + expect(after[recipient][xerc20.symbol]).to.be.equal( + operation.amount.toString(), + ) + + expect(after[adapter.account][xerc20.symbol]).to.be.equal( + `0.0000 ${xerc20.symbol}`, + ) + + expect(before[feemanager][xerc20.symbol]).to.be.equal( + after[feemanager][xerc20.symbol], + ) + }) + + it('Should settle the operation properly and send userdata', async () => { + const operationWithUserData = { + ...operation, + data: '0x12345abcdefc0de1337f', + } + + const newEvent = { + ...event, + data: serializeOperation(operationWithUserData), + } + + const newMetadata = { + preimage: evmEA.getEventPreImage(newEvent), + signature: evmEA.formatEosSignature(evmEA.sign(newEvent)), + } + + const before = getAccountsBalances( + [user, recipient, adapter.account], + [xerc20], + ) + + const beforeAsset = Asset.from(before[recipient][xerc20.symbol]) + + await adapter.contract.actions + .settle([user, no0x(operationWithUserData), no0x(newMetadata)]) + .send(active(user)) + + const after = getAccountsBalances( + [user, recipient, adapter.account], + [xerc20], + ) + + expect(after[recipient][xerc20.symbol]).to.equal( + sum(operation.amount, beforeAsset).toString(), + ) + + expect(after[adapter.account][xerc20.symbol]).to.be.equal( + `0.0000 ${xerc20.symbol}`, + ) + }) + }) }) }) }) From de44cbb221da25d4dd5ac1c5d64bd2920ef4cd02 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 11:59:08 +0100 Subject: [PATCH 06/23] chore(ProofcastEventAttestator): prevent not 0x'ed prefixed input --- .../event-attestator/src/ProofcastEventAttestator.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/javascript/event-attestator/src/ProofcastEventAttestator.js b/javascript/event-attestator/src/ProofcastEventAttestator.js index d85b46e..b55ddaa 100644 --- a/javascript/event-attestator/src/ProofcastEventAttestator.js +++ b/javascript/event-attestator/src/ProofcastEventAttestator.js @@ -62,11 +62,15 @@ class ProofcastEventAttestator { getEvmEventPayload(event) { // EVM event support only: for other chains may be // required to change logic based on version and protocolID - const topics = [0, 1, 2, 3].map( - i => event.topics[i] || zeroPadValue('0x00', 32), + const topics = [0, 1, 2, 3].map(i => + this._0x(event.topics[i] || zeroPadValue('0x00', 32)), ) - return concat([zeroPadValue(event.address, 32), ...topics, event.data]) + return concat([ + zeroPadValue(this._0x(event.address), 32), + ...topics, + this._0x(event.data), + ]) } isEvmEvent(event) { From eeea783243292f614daafcc9fbff304a796cda0c Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 14:36:35 +0100 Subject: [PATCH 07/23] fix(adapter.cpp): rm nonce from `adapter::swap` fn --- cpp/contracts/adapter.cpp | 4 ++-- cpp/contracts/adapter.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/contracts/adapter.cpp b/cpp/contracts/adapter.cpp index a497999..c3105e7 100644 --- a/cpp/contracts/adapter.cpp +++ b/cpp/contracts/adapter.cpp @@ -240,7 +240,7 @@ void adapter::settle(const name& caller, const operation& operation, const metad } } -void adapter::swap(const uint64_t& nonce, const bytes& event_bytes) { +void adapter::swap(const bytes& event_bytes) { require_auth(get_self()); // IMPORTANT: this is for the tests, vert doesn't correctly @@ -324,7 +324,7 @@ void adapter::xerc20_transfer_from_any( ); action_swap _swap{self, {self, "active"_n}}; - _swap.send(storage.nonce, event_bytes); + _swap.send(event_bytes); storage.nonce++; _storage.set(storage, self); diff --git a/cpp/contracts/adapter.hpp b/cpp/contracts/adapter.hpp index cc27547..d0a15e8 100644 --- a/cpp/contracts/adapter.hpp +++ b/cpp/contracts/adapter.hpp @@ -49,7 +49,7 @@ namespace eosio { ACTION setorigin(bytes chain_id, bytes emitter, bytes topic_zero); - ACTION swap(const uint64_t& nonce, const bytes& event_bytes); + ACTION swap(const bytes& event_bytes); ACTION settle(const name& caller, const operation& operation, const metadata& metadata); From 0b5bc47e505bbc764dfb3be2dba4ef327f343afa Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 15:31:04 +0100 Subject: [PATCH 08/23] chore(adapter.test.js): rm `adapter::settle` specific tests --- cpp/test/adapter.test.js | 837 ++++++++++++++++----------------------- 1 file changed, 336 insertions(+), 501 deletions(-) diff --git a/cpp/test/adapter.test.js b/cpp/test/adapter.test.js index 0ed9164..5e43fdf 100644 --- a/cpp/test/adapter.test.js +++ b/cpp/test/adapter.test.js @@ -3,17 +3,13 @@ const { Blockchain, expectToThrow } = require('@eosnetwork/vert') const { Asset } = require('@wharfkit/antelope') const { Symbol } = Asset const { - sum, no0x, active, deploy, errors, bytes32, - getOperation, getSymbolCodeRaw, getAccountCodeRaw, - serializeOperation, - getAccountsBalances, fromEthersPublicKey, getSingletonInstance, } = require('./utils') @@ -25,8 +21,6 @@ const { ProofcastEventAttestator, } = require('@pnetwork/event-attestator') -const LOCAL = 'Local' -const NOT_LOCAL = 'Not-local' const TABLE_STORAGE = 'storage' describe('Adapter tests', () => { @@ -52,19 +46,12 @@ describe('Adapter tests', () => { const evmTopicZero = '66756e6473206172652073616675207361667520736166752073616675202e2e' - const notLocalToken = { + const token = { symbol, - account: '', - maxSupply: Asset.from(0, symbolPrecision), - bytes: '000000000000000000000000810090f35dfa6b18b5eb59d298e2a2443a2811e2', // EVM address - } - - const tokenAccount = `${symbol.toLowerCase()}.token` - const localToken = { - symbol, - account: tokenAccount, + account: `${symbol.toLowerCase()}.token`, maxSupply: Asset.from(maxSupply, symbolPrecision), bytes: no0x(bytes32(toBeHex(String(getSymbolCodeRaw(symbolPrecision))))), + contract: null, } const xerc20 = { @@ -75,11 +62,6 @@ describe('Adapter tests', () => { contract: null, } - const lockbox = { - account: 'lockbox', - contract: null, - } - const adapter = { account: 'adapter', contract: null, @@ -101,517 +83,370 @@ describe('Adapter tests', () => { chainId: Chains(Protocols.Evm).Mainnet, }) - const eosEA = new ProofcastEventAttestator({ - version: Versions.V1, - protocolId: Protocols.Eos, - chainId: Chains(Protocols.Eos).Mainnet, + before(() => { + blockchain.createAccounts(user, evil, issuer, bridge, recipient, feemanager) + + xerc20.contract = deploy( + blockchain, + xerc20.account, + 'contracts/build/xerc20.token', + ) + + token.contract = deploy( + blockchain, + token.account, + 'contracts/build/eosio.token', + ) + + adapter.contract = deploy( + blockchain, + adapter.account, + 'contracts/build/adapter', + ) + + notInitAdapter.contract = deploy( + blockchain, + notInitAdapter.account, + 'contracts/build/adapter', + ) + + receiver.contract = deploy( + blockchain, + receiver.account, + 'contracts/build/test.receiver', + ) }) - ;[NOT_LOCAL].map(_type => { - describe(_type, () => { - before(() => { - blockchain.createAccounts( - user, - evil, - issuer, - bridge, - recipient, - feemanager, + describe('adapter::create', () => { + it('Should throw if called by not authorized account', async () => { + const action = adapter.contract.actions + .create([ + xerc20.account, + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + xerc20.minFee, + ]) + .send(active(evil)) + + await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) + }) + + it('Should throw if tokenByte size is not 32', async () => { + const wrongBytes = 'aa' + try { + await adapter.contract.actions + .create([ + xerc20.account, + xsymbolPrecision, + token.account, + symbolPrecision, + wrongBytes, + xerc20.minFee, + ]) + .send(active(adapter.account)) + + fail() + } catch (_err) { + expect(_err.underlyingError.toString()).to.be.equal( + `Error: Checksum size mismatch, expected 32 bytes got ${wrongBytes.length / 2}`, ) + } + }) + + it('Should throw if xERC20 account does not exist', async () => { + const wrongAccount = 'undeployed' + const action = adapter.contract.actions + .create([ + wrongAccount, + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + xerc20.minFee, + ]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.INVALID_ACCOUNT) + }) + + const deployTokenContracts = async () => { + await xerc20.contract.actions + .create([issuer, xerc20.maxSupply]) + .send(active(xerc20.account)) + + const mintingLimit = Asset.from(1000, xsymbolPrecision) + const burningLimit = Asset.from(600, xsymbolPrecision) + + await xerc20.contract.actions + .setlimits([adapter.account, mintingLimit, burningLimit]) + .send(active(xerc20.account)) - xerc20.contract = deploy( - blockchain, + await token.contract.actions + .create([issuer, token.maxSupply]) + .send(active(token.account)) + } + + it('Should throw if minFee precision does not match', async () => { + await deployTokenContracts() + + const wrongPrecision = precision - 1 + const wrongSymbol = Symbol.fromParts(xsymbol, wrongPrecision) + const wrongMinFee = Asset.from(minFee, wrongSymbol) + const action = adapter.contract.actions + .create([ xerc20.account, - 'contracts/build/xerc20.token', - ) + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + wrongMinFee, + ]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.INVALID_MINFEE_SYMBOL) + }) - adapter.contract = deploy( - blockchain, - adapter.account, - 'contracts/build/adapter', - ) + it('Should throw if minFee symbol does not match', async () => { + const wrongSymbol = Symbol.fromParts('WRONG', precision) + const wrongMinFee = Asset.from(minFee, wrongSymbol) + const action = adapter.contract.actions + .create([ + xerc20.account, + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + wrongMinFee, + ]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.INVALID_MINFEE_SYMBOL) + }) - notInitAdapter.contract = deploy( - blockchain, - notInitAdapter.account, - 'contracts/build/adapter', - ) + it('Should throw if xERC20 symbol is not found on xERC20 account', async () => { + const unknownSymbol = 'XXYY' + const wrongXERC20 = Symbol.fromParts(unknownSymbol, precision) - receiver.contract = deploy( - blockchain, - receiver.account, - 'contracts/build/test.receiver', - ) + const action = adapter.contract.actions + .create([ + xerc20.account, + wrongXERC20, + token.account, + symbolPrecision, + token.bytes, + xerc20.minFee, + ]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.SYMBOL_NOT_FOUND) + }) + + it('Should throw if symbol precision is not correct', async () => { + const wrongPrecision = precision - 1 + const wrongSymbol = Symbol.fromParts(xsymbol, wrongPrecision) + const action = adapter.contract.actions + .create([ + xerc20.account, + wrongSymbol, + token.account, + symbolPrecision, + token.bytes, + Asset.from(10, wrongSymbol), + ]) + .send(active(adapter.account)) + + await expectToThrow(action, errors.INVALID_SYMBOL) + }) - if (_type == LOCAL) { - lockbox.contract = deploy( - blockchain, - lockbox.account, - 'contracts/build/lockbox', - ) - - localToken.contract = deploy( - blockchain, - localToken.account, - 'contracts/build/eosio.token', - ) - } + it('Should create the pair successfully', async () => { + await adapter.contract.actions + .create([ + xerc20.account, + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + xerc20.minFee, + ]) + .send(active(adapter.account)) + + const row = adapter.contract.tables + .regadapter(getAccountCodeRaw(adapter.account)) + .getTableRow(getSymbolCodeRaw(token.maxSupply)) + const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) + const tee = getSingletonInstance(adapter.contract, 'tee') + const mappingsRow = adapter.contract.tables + .mappings(getAccountCodeRaw(adapter.account)) + .getTableRows() + + expect(row).to.be.deep.equal({ + token: token.account, + token_symbol: symbolPrecision.toString(), + token_bytes: token.bytes, + xerc20: xerc20.account, + xerc20_symbol: xsymbolPrecision.toString(), + min_fee: xerc20.minFee.toString(), + }) + expect(storage).be.deep.equal({ + nonce: 0, + feesmanager: '', }) + expect(tee).to.be.undefined + expect(mappingsRow).to.be.deep.equal([]) + }) + + it('Should throw if already created', async () => { + const action = adapter.contract.actions + .create([ + xerc20.account, + xsymbolPrecision, + token.account, + symbolPrecision, + token.bytes, + xerc20.minFee, + ]) + .send(active(adapter.account)) + + await expectToThrow(action, 'eosio_assert: token already registered') + }) + }) + + describe('adapter::setfeemanagr', () => { + it('Should throw if called by not authorized account', async () => { + const action = adapter.contract.actions + .setfeemanagr([feemanager]) + .send(active(evil)) + + await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) + }) - after(async () => { - blockchain.resetTables() - await blockchain.resetVm() + it('Should throw if adapter is not initialized', async () => { + const action = notInitAdapter.contract.actions + .setfeemanagr([feemanager]) + .send(active(notInitAdapter.account)) + + await expectToThrow(action, errors.NOT_INITIALIZED) + }) + + it('Should set the feemanager correctly', async () => { + await adapter.contract.actions + .setfeemanagr([user]) + .send(active(adapter.account)) + + const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) + + expect(storage).be.deep.equal({ + nonce: 0, + feesmanager: user, }) + }) + + it('Should update the feemanager correctly', async () => { + await adapter.contract.actions + .setfeemanagr([feemanager]) + .send(active(adapter.account)) + + const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) - const token = _type === LOCAL ? localToken : notLocalToken - - describe('adapter::create', () => { - it('Should throw if called by not authorized account', async () => { - const action = adapter.contract.actions - .create([ - xerc20.account, - xsymbolPrecision, - token.account, - symbolPrecision, - token.bytes, - xerc20.minFee, - ]) - .send(active(evil)) - - await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) - }) - - it('Should throw if tokenByte size is not 32', async () => { - const wrongBytes = 'aa' - try { - await adapter.contract.actions - .create([ - xerc20.account, - xsymbolPrecision, - token.account, - symbolPrecision, - wrongBytes, - xerc20.minFee, - ]) - .send(active(adapter.account)) - - fail() - } catch (_err) { - expect(_err.underlyingError.toString()).to.be.equal( - `Error: Checksum size mismatch, expected 32 bytes got ${wrongBytes.length / 2}`, - ) - } - }) - - it('Should throw if xERC20 account does not exist', async () => { - const wrongAccount = 'undeployed' - const action = adapter.contract.actions - .create([ - wrongAccount, - xsymbolPrecision, - token.account, - symbolPrecision, - token.bytes, - xerc20.minFee, - ]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.INVALID_ACCOUNT) - }) - - const deployTokenContracts = async () => { - await xerc20.contract.actions - .create([issuer, xerc20.maxSupply]) - .send(active(xerc20.account)) - - const mintingLimit = Asset.from(1000, xsymbolPrecision) - const burningLimit = Asset.from(600, xsymbolPrecision) - - await xerc20.contract.actions - .setlimits([adapter.account, mintingLimit, burningLimit]) - .send(active(xerc20.account)) - - if (_type === LOCAL) { - await localToken.contract.actions - .create([issuer, localToken.maxSupply]) - .send(active(localToken.account)) - } - } - - it('Should throw if minFee precision does not match', async () => { - await deployTokenContracts() - - const wrongPrecision = precision - 1 - const wrongSymbol = Symbol.fromParts(xsymbol, wrongPrecision) - const wrongMinFee = Asset.from(minFee, wrongSymbol) - const action = adapter.contract.actions - .create([ - xerc20.account, - xsymbolPrecision, - token.account, - symbolPrecision, - token.bytes, - wrongMinFee, - ]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.INVALID_MINFEE_SYMBOL) - }) - - it('Should throw if minFee symbol does not match', async () => { - const wrongSymbol = Symbol.fromParts('WRONG', precision) - const wrongMinFee = Asset.from(minFee, wrongSymbol) - const action = adapter.contract.actions - .create([ - xerc20.account, - xsymbolPrecision, - token.account, - symbolPrecision, - token.bytes, - wrongMinFee, - ]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.INVALID_MINFEE_SYMBOL) - }) - - it('Should throw if xERC20 symbol is not found on xERC20 account', async () => { - const unknownSymbol = 'XXYY' - const wrongXERC20 = Symbol.fromParts(unknownSymbol, precision) - - const action = adapter.contract.actions - .create([ - xerc20.account, - wrongXERC20, - token.account, - symbolPrecision, - token.bytes, - xerc20.minFee, - ]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.SYMBOL_NOT_FOUND) - }) - - it('Should throw if symbol precision is not correct', async () => { - const wrongPrecision = precision - 1 - const wrongSymbol = Symbol.fromParts(xsymbol, wrongPrecision) - const action = adapter.contract.actions - .create([ - xerc20.account, - wrongSymbol, - token.account, - symbolPrecision, - token.bytes, - Asset.from(10, wrongSymbol), - ]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.INVALID_SYMBOL) - }) - - it('Should create the pair successfully', async () => { - await adapter.contract.actions - .create([ - xerc20.account, - xsymbolPrecision, - token.account, - symbolPrecision, - token.bytes, - xerc20.minFee, - ]) - .send(active(adapter.account)) - - const row = adapter.contract.tables - .regadapter(getAccountCodeRaw(adapter.account)) - .getTableRow(getSymbolCodeRaw(token.maxSupply)) - const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) - const tee = getSingletonInstance(adapter.contract, 'tee') - const mappingsRow = adapter.contract.tables - .mappings(getAccountCodeRaw(adapter.account)) - .getTableRows() - - expect(row).to.be.deep.equal({ - token: token.account, - token_symbol: symbolPrecision.toString(), - token_bytes: token.bytes, - xerc20: xerc20.account, - xerc20_symbol: xsymbolPrecision.toString(), - min_fee: xerc20.minFee.toString(), - }) - expect(storage).be.deep.equal({ - nonce: 0, - feesmanager: '', - }) - expect(tee).to.be.undefined - expect(mappingsRow).to.be.deep.equal([]) - }) - - it('Should throw if already created', async () => { - const action = adapter.contract.actions - .create([ - xerc20.account, - xsymbolPrecision, - token.account, - symbolPrecision, - token.bytes, - xerc20.minFee, - ]) - .send(active(adapter.account)) - - await expectToThrow(action, 'eosio_assert: token already registered') - }) + expect(storage).be.deep.equal({ + nonce: 0, + feesmanager: feemanager, }) + }) + }) - describe('adapter::setfeemanagr', () => { - it('Should throw if called by not authorized account', async () => { - const action = adapter.contract.actions - .setfeemanagr([feemanager]) - .send(active(evil)) + describe('adapter::settee', () => { + const teePubKey = fromEthersPublicKey(evmEA.signingKey.compressedPublicKey) + const attestation = '' + it('Should throw if called by not authorized account', async () => { + const action = adapter.contract.actions + .settee([teePubKey, attestation]) + .send(active(evil)) - await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) - }) + await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) + }) - it('Should throw if adapter is not initialized', async () => { - const action = notInitAdapter.contract.actions - .setfeemanagr([feemanager]) - .send(active(notInitAdapter.account)) + it('Should set the tee pubKey and attestation correctly', async () => { + await adapter.contract.actions + .settee([teePubKey, attestation]) + .send(active(adapter.account)) - await expectToThrow(action, errors.NOT_INITIALIZED) - }) + const tee = getSingletonInstance(adapter.contract, 'tee') - it('Should set the feemanager correctly', async () => { - await adapter.contract.actions - .setfeemanagr([user]) - .send(active(adapter.account)) + expect(tee.key).to.be.equal(teePubKey.toString()) + expect(tee.attestation).to.be.equal(attestation) + }) + }) - const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) + describe('adapter::setorigin', () => { + const originChainId = no0x(bytes32(Chains(Protocols.Evm).Mainnet)) - expect(storage).be.deep.equal({ - nonce: 0, - feesmanager: user, - }) - }) + it('Should throw if called by not authorized account', async () => { + const action = adapter.contract.actions + .setorigin([originChainId, evmAdapter, evmTopicZero]) + .send(active(evil)) - it('Should update the feemanager correctly', async () => { - await adapter.contract.actions - .setfeemanagr([feemanager]) - .send(active(adapter.account)) + await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) + }) - const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) + it('Should throw if emitter is not 32 bytes', async () => { + const wrong = 'C0FFEE' + const action = adapter.contract.actions + .setorigin([originChainId, wrong, evmTopicZero]) + .send(active(adapter.account)) - expect(storage).be.deep.equal({ - nonce: 0, - feesmanager: feemanager, - }) - }) - }) + await expectToThrow(action, errors.EXPECTED_32_BYTES('emitter')) + }) - describe('adapter::settee', () => { - const teePubKey = fromEthersPublicKey( - evmEA.signingKey.compressedPublicKey, - ) - const attestation = '' - it('Should throw if called by not authorized account', async () => { - const action = adapter.contract.actions - .settee([teePubKey, attestation]) - .send(active(evil)) + it('Should throw if topic zero is not 32 bytes', async () => { + const wrong = 'C0FFEE' + const action = adapter.contract.actions + .setorigin([originChainId, evmAdapter, wrong]) + .send(active(adapter.account)) - await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) - }) + await expectToThrow(action, errors.EXPECTED_32_BYTES('topic zero')) + }) - it('Should set the tee pubKey and attestation correctly', async () => { - await adapter.contract.actions - .settee([teePubKey, attestation]) - .send(active(adapter.account)) + it('Should throw if origin chain id is not 32 bytes', async () => { + const wrong = 1 + const action = adapter.contract.actions + .setorigin([wrong, evmAdapter, evmTopicZero]) + .send(active(adapter.account)) - const tee = getSingletonInstance(adapter.contract, 'tee') + await expectToThrow(action, errors.EXPECTED_32_BYTES('chain_id')) + }) - expect(tee.key).to.be.equal(teePubKey.toString()) - expect(tee.attestation).to.be.equal(attestation) - }) - }) + it('Should set the origin details correctly', async () => { + const anAddress = no0x( + bytes32('0xe396757ec7e6ac7c8e5abe7285dde47b98f22db8'), + ) + await adapter.contract.actions + .setorigin([originChainId, anAddress, evmTopicZero]) + .send(active(adapter.account)) + + const row = adapter.contract.tables + .mappings(getAccountCodeRaw(adapter.account)) + .getTableRow(stripZerosLeft(`0x${originChainId}`)) + + expect(row.chain_id).to.be.equal(originChainId) + expect(row.emitter).to.be.equal(anAddress) + expect(row.topic_zero).to.be.equal(evmTopicZero) + }) - describe('adapter::setorigin', () => { - const originChainId = no0x(bytes32(Chains(Protocols.Evm).Mainnet)) - - it('Should throw if called by not authorized account', async () => { - const action = adapter.contract.actions - .setorigin([originChainId, evmAdapter, evmTopicZero]) - .send(active(evil)) - - await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) - }) - - it('Should throw if emitter is not 32 bytes', async () => { - const wrong = 'C0FFEE' - const action = adapter.contract.actions - .setorigin([originChainId, wrong, evmTopicZero]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.EXPECTED_32_BYTES('emitter')) - }) - - it('Should throw if topic zero is not 32 bytes', async () => { - const wrong = 'C0FFEE' - const action = adapter.contract.actions - .setorigin([originChainId, evmAdapter, wrong]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.EXPECTED_32_BYTES('topic zero')) - }) - - it('Should throw if origin chain id is not 32 bytes', async () => { - const wrong = 1 - const action = adapter.contract.actions - .setorigin([wrong, evmAdapter, evmTopicZero]) - .send(active(adapter.account)) - - await expectToThrow(action, errors.EXPECTED_32_BYTES('chain_id')) - }) - - it('Should set the origin details correctly', async () => { - const anAddress = no0x( - bytes32('0xe396757ec7e6ac7c8e5abe7285dde47b98f22db8'), - ) - await adapter.contract.actions - .setorigin([originChainId, anAddress, evmTopicZero]) - .send(active(adapter.account)) - - const row = adapter.contract.tables - .mappings(getAccountCodeRaw(adapter.account)) - .getTableRow(stripZerosLeft(`0x${originChainId}`)) - - expect(row.chain_id).to.be.equal(originChainId) - expect(row.emitter).to.be.equal(anAddress) - expect(row.topic_zero).to.be.equal(evmTopicZero) - }) - - it('Should update the origin details correctly', async () => { - await adapter.contract.actions - .setorigin([originChainId, evmAdapter, evmTopicZero]) - .send(active(adapter.account)) - - const emitterRow = adapter.contract.tables - .mappings(getAccountCodeRaw(adapter.account)) - .getTableRow(stripZerosLeft(`0x${originChainId}`)) - - expect(emitterRow.chain_id).to.be.equal(originChainId) - expect(emitterRow.emitter).to.be.equal(evmAdapter) - expect(emitterRow.topic_zero).to.be.equal(evmTopicZero) - }) - }) + it('Should update the origin details correctly', async () => { + await adapter.contract.actions + .setorigin([originChainId, evmAdapter, evmTopicZero]) + .send(active(adapter.account)) - describe('adapter::settle', () => { - const operation = getOperation({ - blockId: - '7e21ba208ea2a072bad2d011dbc3a9f870c574a66203d84bde926fcf85756d78', - txId: '2e3704b180feda25af9dfe50793e292fd99d644aa505c3d170fa69407091dbd3', - nonce: 0, - token: '0x810090f35dfa6b18b5eb59d298e2a2443a2811e2', - originChainId: Chains(Protocols.Evm).Mainnet, // EVM mainnet chain id - destinationChainId: Chains(Protocols.Eos).Mainnet, - amount: Asset.from(5.87190615, xsymbolPrecision), - sender: - '000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266', - recipient, - data: '', - }) - - const event = { - blockHash: operation.blockId, - transactionHash: operation.txId, - address: evmAdapter, - topics: [evmTopicZero], - data: serializeOperation(operation), - } - const preimage = no0x(evmEA.getEventPreImage(event)) - const signature = no0x(evmEA.formatEosSignature(evmEA.sign(event))) - - const metadata = { preimage, signature } - - it('Should reject if adapter and token do not match', async () => { - const wrongTokenBytes = no0x(bytes32('0x')) - const action = adapter.contract.actions - .settle([ - user, - { ...no0x(operation), token: wrongTokenBytes }, - metadata, - ]) - .send(active(user)) - - await expectToThrow(action, errors.INVALID_TOKEN) - }) - - it('Should settle the operation properly', async () => { - const before = getAccountsBalances( - [user, recipient, adapter.account, feemanager], - [xerc20], - ) - - await adapter.contract.actions - .settle([user, no0x(operation), metadata]) - .send(active(user)) - - const after = getAccountsBalances( - [user, recipient, adapter.account, feemanager], - [xerc20], - ) - - expect(after[recipient][xerc20.symbol]).to.be.equal( - operation.amount.toString(), - ) - - expect(after[adapter.account][xerc20.symbol]).to.be.equal( - `0.0000 ${xerc20.symbol}`, - ) - - expect(before[feemanager][xerc20.symbol]).to.be.equal( - after[feemanager][xerc20.symbol], - ) - }) - - it('Should settle the operation properly and send userdata', async () => { - const operationWithUserData = { - ...operation, - data: '0x12345abcdefc0de1337f', - } - - const newEvent = { - ...event, - data: serializeOperation(operationWithUserData), - } - - const newMetadata = { - preimage: evmEA.getEventPreImage(newEvent), - signature: evmEA.formatEosSignature(evmEA.sign(newEvent)), - } - - const before = getAccountsBalances( - [user, recipient, adapter.account], - [xerc20], - ) - - const beforeAsset = Asset.from(before[recipient][xerc20.symbol]) - - await adapter.contract.actions - .settle([user, no0x(operationWithUserData), no0x(newMetadata)]) - .send(active(user)) - - const after = getAccountsBalances( - [user, recipient, adapter.account], - [xerc20], - ) - - expect(after[recipient][xerc20.symbol]).to.equal( - sum(operation.amount, beforeAsset).toString(), - ) - - expect(after[adapter.account][xerc20.symbol]).to.be.equal( - `0.0000 ${xerc20.symbol}`, - ) - }) - }) + const emitterRow = adapter.contract.tables + .mappings(getAccountCodeRaw(adapter.account)) + .getTableRow(stripZerosLeft(`0x${originChainId}`)) + + expect(emitterRow.chain_id).to.be.equal(originChainId) + expect(emitterRow.emitter).to.be.equal(evmAdapter) + expect(emitterRow.topic_zero).to.be.equal(evmTopicZero) }) }) }) From f05ef0ab6f821c54e5449074d3ecc062cd1a01af Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 17:17:47 +0100 Subject: [PATCH 09/23] fix(xerc20.token.cpp): prevent errors when lockbox do not exist --- cpp/contracts/xerc20.token.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/contracts/xerc20.token.cpp b/cpp/contracts/xerc20.token.cpp index b4086ce..868136b 100644 --- a/cpp/contracts/xerc20.token.cpp +++ b/cpp/contracts/xerc20.token.cpp @@ -82,7 +82,7 @@ void xtoken::burn( const name& caller, const asset& quantity, const string& memo const auto& st = *existing; lockbox_singleton _lockbox( get_self(), get_self().value ); - auto lockbox = _lockbox.get(); + auto lockbox = _lockbox.get_or_default(name(0)); bridges bridgestable( get_self(), get_self().value ); auto idx = bridgestable.get_index(); auto itr = idx.lower_bound( quantity.symbol.code().raw() ); From 881baf9d6e9e0011b4a4979612c0c82b59ca32e1 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 17:18:28 +0100 Subject: [PATCH 10/23] fix(xerc20.token.test.js): use maxSupply as Asset --- cpp/test/xerc20.token.test.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cpp/test/xerc20.token.test.js b/cpp/test/xerc20.token.test.js index 524aa97..376e15d 100644 --- a/cpp/test/xerc20.token.test.js +++ b/cpp/test/xerc20.token.test.js @@ -7,6 +7,7 @@ const { active, getSymbolCodeRaw, getAccountCodeRaw, + precision, } = require('./utils/eos-ext') const errors = require('./utils/errors') @@ -15,8 +16,9 @@ const round = (_value, _decimals) => describe('xerc20.token', () => { const symbol = 'TKN' + const symbolPrecision = precision(0, symbol) const account = `${symbol.toLowerCase()}.token` - const maxSupply = `500000000 ${symbol}` + const maxSupply = Asset.from(500000000, symbolPrecision) const blockchain = new Blockchain() const DURATION = 24 * 60 * 60 // 1 day @@ -40,7 +42,7 @@ describe('xerc20.token', () => { expect(row).to.be.deep.equal({ supply: `0 ${symbol}`, - max_supply: maxSupply, + max_supply: maxSupply.toString(), issuer: issuer, }) }) @@ -168,6 +170,7 @@ describe('xerc20.token', () => { await xerc20.actions .transfer([recipient, bridge, quantity, memo]) .send(active(recipient)) + await xerc20.actions.burn([bridge, quantity, memo]).send(active(bridge)) const bridgeLimits = xerc20.tables From f0de427c252b048e578b1d061712399673e5cb8f Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 17:19:08 +0100 Subject: [PATCH 11/23] chore(utils): factor out getSwapMemo --- cpp/test/utils/get-swap-memo.js | 3 +++ cpp/test/utils/index.js | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 cpp/test/utils/get-swap-memo.js diff --git a/cpp/test/utils/get-swap-memo.js b/cpp/test/utils/get-swap-memo.js new file mode 100644 index 0000000..c254b16 --- /dev/null +++ b/cpp/test/utils/get-swap-memo.js @@ -0,0 +1,3 @@ +const R = require('ramda') +module.exports.getSwapMemo = (sender, destinationChainId, recipient, data) => + `${sender},${destinationChainId},${recipient},${R.isEmpty(data) ? '0' : '1'}` diff --git a/cpp/test/utils/index.js b/cpp/test/utils/index.js index 265a221..5c83414 100644 --- a/cpp/test/utils/index.js +++ b/cpp/test/utils/index.js @@ -5,6 +5,7 @@ const bytesUtils = require('./bytes-utils.js') const wharfkitExt = require('./wharfkit-ext.js') const hexToPubkey = require('./hex-to-pubkey.js') const hexToPublicKey = require('./hex-to-pubkey') +const getSwapMemo = require('./get-swap-memo.js') const getEventBytes = require('./get-event-bytes.js') const getTokenBalance = require('./get-token-balance.js') const getMetadataSample = require('./get-metadata-sample.js') @@ -17,6 +18,7 @@ module.exports = { ...bytesUtils, ...hexToPubkey, ...wharfkitExt, + ...getSwapMemo, ...getEventBytes, ...hexToPublicKey, ...getTokenBalance, From ef9f251951708aa86ea16fa9a3cd8974b8103c16 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 17:32:14 +0100 Subject: [PATCH 12/23] chore(adapter.cpp): rm marker --- cpp/contracts/adapter.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp/contracts/adapter.cpp b/cpp/contracts/adapter.cpp index c3105e7..057b822 100644 --- a/cpp/contracts/adapter.cpp +++ b/cpp/contracts/adapter.cpp @@ -247,7 +247,6 @@ void adapter::swap(const bytes& event_bytes) { // deserialize the event_bytes arg, so we'll get it from // the bc.console // NOTE: performance are not affected by this - print("adapter_swap_event_bytes:"); printhex(event_bytes.data(), event_bytes.size()); } From 43d107d955fe26a1e4e5e6be4b3d6db0b3cd98bf Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 18:20:37 +0100 Subject: [PATCH 13/23] chore(adapter-local.test.js): simplify --- cpp/test/adapter-local.test.js | 472 ++++++++++++++------------------- cpp/test/utils/bytes-utils.js | 54 +++- 2 files changed, 253 insertions(+), 273 deletions(-) diff --git a/cpp/test/adapter-local.test.js b/cpp/test/adapter-local.test.js index 6e98fab..8841983 100644 --- a/cpp/test/adapter-local.test.js +++ b/cpp/test/adapter-local.test.js @@ -1,61 +1,75 @@ +const R = require('ramda') const { expect } = require('chai') -const { Blockchain, expectToThrow } = require('@eosnetwork/vert') +const { Asset, Name } = require('@wharfkit/antelope') +const { + Blockchain, + expectToThrow, + nameToBigInt, + logExecutionTrace, +} = require('@eosnetwork/vert') const { deploy } = require('./utils/deploy') -const R = require('ramda') const { + no0x, active, + errors, + bytes32, precision, - getAccountCodeRaw, - getSymbolCodeRaw, - getSingletonInstance, + substract, + getSwapMemo, prettyTrace, -} = require('./utils/eos-ext') -const { - getXbytesHex, hexToString, + getXbytesHex, + getEventBytes, removeNullChars, -} = require('./utils/bytes-utils') -const { getEventBytes } = require('./utils/get-event-bytes') -const { substract } = require('./utils/wharfkit-ext') -const { getAccountsBalances } = require('./utils/get-token-balance') -const errors = require('./utils/errors') -const { no0x } = require('./utils') -const ethers = require('ethers') - -const getSwapMemo = (sender, destinationChainId, recipient, data) => - `${sender},${destinationChainId},${recipient},${R.isEmpty(data) ? '0' : '1'}` - -const attestation = 'deadbeef' + getSymbolCodeRaw, + getAccountsBalances, + getSingletonInstance, + _0x, + fromEthersPublicKey, + deserializeEventBytes, + getAccountCodeRaw, + logExecutionTraces, +} = require('./utils') -describe('Adapter EOS -> ETH testing', () => { +const { toBeHex } = require('ethers') +const { + Protocols, + Chains, + ProofcastEventAttestator, + Versions, +} = require('@pnetwork/event-attestator') + +describe('Adapter Testing - Local deployment', () => { + const decimals = 4 const symbol = 'TKN' - const minFee = `0.0010 X${symbol}` - const precision4 = precision(4) - const maxSupply = '500000000.0000' - const tokenMaxSupply = '500000000.0000' - const userInitialBalance = `1000.0000 ${symbol}` - const tokenBytes = no0x( - ethers.zeroPadValue( - ethers.toBeHex(getSymbolCodeRaw(`0.0000 ${symbol}`).toString()), - 32, - ), - ) + const xsymbol = `X${symbol}` + const symbolPrecision = precision(decimals, symbol) + const xsymbolPrecision = precision(decimals, xsymbol) + const minFee = Asset.from(0.0018, xsymbolPrecision) + const maxSupply = 500000000 + const userInitialBalance = Asset.from(1000, symbolPrecision) const TABLE_STORAGE = 'storage' const FEE_BASIS_POINTS = 1750 const FEE_BASIS_POINTS_DIVISOR = 1000000 + const evmOriginChainId = Chains(Protocols.Evm).Mainnet + const evmAdapter = + '000000000000000000000000bcf063a9eb18bc3c6eb005791c61801b7cb16fe4' + const evmTopicZero = + '66756e6473206172652073616675207361667520736166752073616675202e2e' + const token = { symbol: symbol, account: `${symbol.toLowerCase()}.token`, - maxSupply: `${maxSupply} ${symbol}`, - bytes: tokenBytes, + maxSupply: Asset.from(maxSupply, symbolPrecision), + bytes: no0x(bytes32(toBeHex(Number(getSymbolCodeRaw(symbolPrecision))))), contract: null, } const xerc20 = { - symbol: `X${symbol}`, - account: `x${symbol.toLowerCase()}.token`, - maxSupply: `${maxSupply} X${symbol}`, + symbol: xsymbol, + account: `${xsymbol.toLowerCase()}.token`, + maxSupply: Asset.from(maxSupply, xsymbolPrecision), contract: null, } @@ -74,6 +88,12 @@ describe('Adapter EOS -> ETH testing', () => { contract: null, } + const evmEA = new ProofcastEventAttestator({ + version: Versions.V1, + protocolId: Protocols.Evm, + chainId: Chains(Protocols.Evm).Mainnet, + }) + const blockchain = new Blockchain() const user = 'user' @@ -85,26 +105,31 @@ describe('Adapter EOS -> ETH testing', () => { before(async () => { blockchain.createAccounts(user, evil, issuer, bridge, recipient, feemanager) + lockbox.contract = deploy( blockchain, lockbox.account, 'contracts/build/lockbox', ) + token.contract = deploy( blockchain, token.account, 'contracts/build/eosio.token', ) + xerc20.contract = deploy( blockchain, xerc20.account, 'contracts/build/xerc20.token', ) + adapter.contract = deploy( blockchain, adapter.account, 'contracts/build/adapter', ) + receiver.contract = deploy( blockchain, receiver.account, @@ -125,12 +150,13 @@ describe('Adapter EOS -> ETH testing', () => { await lockbox.contract.actions .create([ xerc20.account, - precision4(xerc20.symbol), + xsymbolPrecision, token.account, - precision4(token.symbol), + symbolPrecision, ]) .send(active(lockbox.account)) + // Fund the user await token.contract.actions .issue([issuer, userInitialBalance, memo]) .send(active(issuer)) @@ -139,6 +165,15 @@ describe('Adapter EOS -> ETH testing', () => { .transfer([issuer, user, userInitialBalance, memo]) .send(active(issuer)) + // Fund the attacker + await token.contract.actions + .issue([issuer, userInitialBalance, memo]) + .send(active(issuer)) + + await token.contract.actions + .transfer([issuer, evil, userInitialBalance, memo]) + .send(active(issuer)) + await xerc20.contract.actions .setlockbox(lockbox) .send(active(xerc20.account)) @@ -158,66 +193,47 @@ describe('Adapter EOS -> ETH testing', () => { await adapter.contract.actions .create([ xerc20.account, - precision4(xerc20.symbol), + xsymbolPrecision, token.account, - precision4(token.symbol), + symbolPrecision, token.bytes, minFee, ]) .send(active(adapter.account)) + }) + it('Should set the fee manager successfully', async () => { await adapter.contract.actions .setfeemanagr([feemanager]) .send(active(adapter.account)) + }) - const row = getSingletonInstance(adapter.contract, 'regadapter') - - const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) - - expect(row).to.be.deep.equal({ - token: token.account, - token_symbol: precision4(token.symbol), - token_bytes: token.bytes, - xerc20: xerc20.account, - xerc20_symbol: precision4(xerc20.symbol), - min_fee: minFee, - }) + it('Should add the tee public key successfully', async () => { + const teePubKey = fromEthersPublicKey( + evmEA.signingKey.compressedPublicKey, + ) - expect(row).to.be.deep.equal({ - token: token.account, - token_symbol: precision4(token.symbol), - token_bytes: token.bytes, - xerc20: xerc20.account, - xerc20_symbol: precision4(xerc20.symbol), - min_fee: minFee, - }) + const attestation = '' + await adapter.contract.actions + .settee([teePubKey, attestation]) + .send(active(adapter.account)) + }) - expect(storage).be.deep.equal({ - nonce: 0, - feesmanager: feemanager, - }) + it('Should add the origin details successfully', async () => { + await adapter.contract.actions + .setorigin([no0x(bytes32(evmOriginChainId)), evmAdapter, evmTopicZero]) + .send(active(adapter.account)) }) }) describe('adapter::swap', () => { - it('Should revert when calling the swap function directly', async () => { - const nonce = 3 - const eventBytes = '00000666' - const action = adapter.contract.actions - .swap([nonce, eventBytes]) - .send(active(evil)) - - await expectToThrow(action, errors.AUTH_MISSING(adapter.account)) - }) - + const data = '' + const recipient = '0x68bbed6a47194eff1cf514b50ea91895597fc91e' + const destinationChainId = Chains(Protocols.Evm).Mainnet + const memo = getSwapMemo(user, bytes32(destinationChainId), recipient, data) + const amount = 10 + const quantity = Asset.from(amount, symbolPrecision) it('Should swap correctly', async () => { - const data = '' - const recipient = '0x68bbed6a47194eff1cf514b50ea91895597fc91e' - const destinationChainId = ethers.zeroPadValue('0x01', 32) - const memo = getSwapMemo(user, destinationChainId, recipient, data) - const amount = '10.0000' - const quantity = `${amount} ${symbol}` - const before = getAccountsBalances( [user, lockbox.account, adapter.account, feemanager], [token, xerc20], @@ -237,39 +253,26 @@ describe('Adapter EOS -> ETH testing', () => { after.storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) expect( - substract( - before.user[token.symbol], - after.user[token.symbol], - ).toString(), - ).to.be.equal(quantity) + substract(before.user[token.symbol], after.user[token.symbol]), + ).to.be.deep.equal(quantity) expect( - substract( - after.lockbox[token.symbol], - before.lockbox[token.symbol], - ).toString(), - ).to.be.equal(quantity) + substract(after.lockbox[token.symbol], before.lockbox[token.symbol]), + ).to.be.deep.equal(quantity) expect( - substract( - after.lockbox[xerc20.symbol], - before.lockbox[xerc20.symbol], - ).toString(), - ).to.be.equal(`0.0000 ${xerc20.symbol}`) + substract(after.lockbox[xerc20.symbol], before.lockbox[xerc20.symbol]), + ).to.be.deep.equal(Asset.from(0, xsymbolPrecision)) - const intFees = ( - (parseInt(amount) * FEE_BASIS_POINTS) / - FEE_BASIS_POINTS_DIVISOR - ).toFixed(4) - - const fees = `${intFees} ${xerc20.symbol}` + const intFees = (amount * FEE_BASIS_POINTS) / FEE_BASIS_POINTS_DIVISOR + const fees = Asset.from(intFees, xsymbolPrecision) expect( substract( after.feemanager[xerc20.symbol], before.feemanager[xerc20.symbol], - ).toString(), - ).to.be.equal(fees) + ), + ).to.be.deep.equal(fees) expect(after.storage.nonce).to.be.equal(before.storage.nonce + 1) @@ -281,174 +284,99 @@ describe('Adapter EOS -> ETH testing', () => { expect(possibleSwap['First Receiver']).to.be.equal(adapter.account) expect(possibleSwap['Sender']).to.be.equal(adapter.account) - const eventBytes = getEventBytes(adapter.contract) - const expectedEventBytes = - '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746b6e2e746f6b656e00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000008a88f6dc465640000000000000000000000000000000000000000000000000000000000075736572000000000000000000000000000000000000000000000000000000000000002a307836386262656436613437313934656666316366353134623530656139313839353539376663393165' - expect(eventBytes).to.be.equal(expectedEventBytes) - - offset = 0 - const nonce = getXbytesHex(eventBytes, offset, 32) - offset += 32 - const swapToken = getXbytesHex(eventBytes, offset, 32) - offset += 32 - const destChainId = getXbytesHex(eventBytes, offset, 32) - offset += 32 - const netAmount = getXbytesHex(eventBytes, offset, 32) - offset += 32 - const swapSender = getXbytesHex(eventBytes, offset, 32) - offset += 32 - const recipientLen = getXbytesHex(eventBytes, offset, 32) - offset += 32 - const swapRecipient = getXbytesHex( - eventBytes, - offset, - parseInt(recipientLen, 16), - ) - offset += parseInt(recipientLen, 16) - const userData = eventBytes.slice(offset * 2, eventBytes.length) - - const expectedAmount = (parseInt(amount) - intFees) * 10 ** 18 - expect(parseInt(nonce)).to.be.equal(before.storage.nonce) - expect(removeNullChars(hexToString(swapToken))).to.be.equal(token.account) - expect(destChainId).to.be.equal(destinationChainId.slice(2)) - expect(parseInt(netAmount, 16)).to.be.equal(expectedAmount) - expect(removeNullChars(hexToString(swapSender))).to.be.equal(user) - expect(removeNullChars(hexToString(swapRecipient))).to.be.equal(recipient) - expect(removeNullChars(hexToString(userData))).to.be.equal(data) + const eventBytes = adapter.contract.bc.console + const deserialized = deserializeEventBytes(eventBytes) + + expect(deserialized.nonce).to.be.equal(before.storage.nonce) + expect(deserialized.token).to.be.equal(token.account) + expect(deserialized.destinationChainid).to.be.equal(destinationChainId) + const expectedAmount = (amount - intFees) * 10 ** 18 + expect(deserialized.amount).to.be.equal(expectedAmount) + expect(deserialized.sender).to.be.equal(user) + expect(deserialized.recipient).to.be.equal(recipient) + expect(deserialized.data).to.be.equal(data) }) - // TODO: test adduserdata + swap actions in - // succession once the above is fixed - // it('Should swap with userdata', async () => {}) - }) + describe('adapter::adduserdata', () => { + const data = Buffer.from('More coffee plz', 'utf-8').toString('hex') + const memo = getSwapMemo( + user, + bytes32(destinationChainId), + recipient, + data, + ) + const amount = 10 + const quantity = Asset.from(amount, symbolPrecision) + + it('Should revert when missing userdata record', async () => { + const action = token.contract.actions + .transfer([user, adapter.account, quantity, memo]) + .send(active(user)) + + await expectToThrow(action, errors.USER_DATA_RECORD_NOT_FOUND) + }) + + it('Should revert when somebody calls adduserdata on their behalf', async () => { + const action = adapter.contract.actions + .adduserdata([user, data]) + .send(active(evil)) + + await expectToThrow(action, errors.AUTH_MISSING(user)) + }) + + it('Should revert when the payload is empty', async () => { + const empty = [] + const action = adapter.contract.actions + .adduserdata([user, empty]) + .send(active(user)) + + await expectToThrow(action, errors.INVALID_PAYLOAD) + }) + + it('Should add user data successfully', async () => { + const temporary = Buffer.from('))=33', 'utf-8').toString('hex') + await adapter.contract.actions + .adduserdata([user, temporary]) + .send(active(user)) + + const rows = adapter.contract.tables + .userdata(nameToBigInt(user)) + .getTableRows() - // describe('adapter::settle', () => { - // it('Should settle the operation properly and send userdata', async () => { - // const quantity = `10.000000 TKN` - // const normalizedAmount = ethers - // .parseUnits(Asset.from(quantity).units.toString(), 18) - // .toString() - - // const operation = getOperationSample({ - // amount: normalizedAmount, - // }) - - // const metadata = getMetadataSample() - - // const before = getAccountsBalances( - // [user, recipient, lockbox.account, adapter.account], - // [token, xerc20], - // ) - - // const compressed = Uint8Array.from(, 16 - // Buffer.from( - // '0380472f799469d9af8790307a022802785c2b1e2f9c0930bdf9bafe193245e7a3', - // 'hex', - // ), - // ) - // const pubKey = PublicKey.from({ type: 'K1', compressed }) - // await adapter.contract.actions - // .settee([pubKey, attestation]) - // .send(active(adapter.account)) - - // const normalizedOriginChainId = hexStringToBytes('0000000000000000000000000000000000000000000000000000000000000001') - // const normalizedOriginAdapter = hexStringToBytes('000000000000000000000000cc9676b9bf25ce45a3a5f88205239afddecf1bc7') - // const normalizeTopicZero = hexStringToBytes('9b706941b48091a1c675b439064f40b9d43c577d9c7134cce93179b9b0bf2a52') - - // await adapter.contract.actions - // .setemitter([normalizedOriginChainId, normalizedOriginAdapter]) - // .send(active(adapter.account)) - - // await adapter.contract.actions - // .settopiczero([normalizedOriginChainId, normalizeTopicZero]) - // .send(active(adapter.account)) - - // await adapter.contract.actions - // .settle([user, operation, metadata]) - // .send(active(user)) - - // const after = getAccountsBalances( - // [user, recipient, lockbox.account, adapter.account], - // [token, xerc20], - // ) - - // console.log(before) - - // console.log(adapter.contract.bc.console) - - // expect( - // substract( - // after[recipient][token.symbol], - // before[recipient][token.symbol], - // ).toString(), - // ).to.be.equal(quantity) - - // expect( - // substract( - // after[recipient][xerc20.symbol], - // before[recipient][xerc20.symbol], - // ).toString(), - // ).to.be.equal(quantity) - - // expect( - // substract( - // before[lockbox.account][token.symbol], - // after[lockbox.account][token.symbol], - // ).toString(), - // ).to.be.equal(quantity) - - // expect(after[lockbox.account][xerc20.symbol]).to.be.equal( - // `0.0000 ${xerc20.symbol}`, - // ) - - // expect(after[adapter.account][token.symbol]).to.be.equal( - // `0.0000 ${token.symbol}`, - // ) - // expect(after[adapter.account][xerc20.symbol]).to.be.equal( - // `0.0000 ${xerc20.symbol}`, - // ) - // }) - - // it('Should send userdata to a receiver contract', async () => { - // const quantity = `1.0000 ${token.symbol}` - // const normalizedAmount = ethers - // .parseUnits(Asset.from(quantity).units.toString(), 18) - // .toString() - - // const metadata = getMetadataSample() - // const operation = getOperationSample({ - // amount: normalizedAmount, - // data: 'c0ffeec0ffeec0ffee', - // recipient: receiver.account, - // }) - - // const before = getAccountsBalances([receiver.account], [token, xerc20]) - - // // Fill in some tokens as collateral - // await token.contract.actions - // .transfer([user, lockbox.account, quantity, '']) - // .send(active(user)) - - // await adapter.contract.actions - // .settle([user, operation, metadata]) - // .send(active(user)) - - // const after = getAccountsBalances([receiver.account], [token, xerc20]) - // const receiverResults = receiver.contract.tables - // .results(getAccountCodeRaw(receiver.account)) - // .getTableRow(0n) - - // expect( - // substract( - // after[receiver.account][token.symbol], - // before[receiver.account][token.symbol], - // ).toString(), - // ).to.be.equal(quantity) - - // expect(receiverResults).to.be.deep.equal({ - // id: 0, - // data: operation.data, - // }) - // }) - // }) + expect(R.last(rows).id).to.be.equal(1) + expect(R.last(rows).payload).to.be.equal(temporary) + }) + + it('Should change userdata to another one successfully', async () => { + await adapter.contract.actions + .adduserdata([user, data]) + .send(active(user)) + + const rows = adapter.contract.tables + .userdata(nameToBigInt(user)) + .getTableRows() + + expect(R.last(rows).id).to.be.equal(1) + expect(R.last(rows).payload).to.be.equal(data) + }) + + it('Should send user data successfully', async () => { + await token.contract.actions + .transfer([user, adapter.account, quantity, memo]) + .send(active(user)) + + const eventBytes = deserializeEventBytes(adapter.contract.bc.console) + + expect(eventBytes.nonce).to.be.equal(1) + expect(eventBytes.token).to.be.equal(token.account) + expect(eventBytes.destinationChainid).to.be.equal(destinationChainId) + const intFees = (amount * FEE_BASIS_POINTS) / FEE_BASIS_POINTS_DIVISOR + const expectedAmount = (amount - intFees) * 10 ** 18 + expect(eventBytes.amount).to.be.equal(expectedAmount) + expect(eventBytes.sender).to.be.equal(user) + expect(eventBytes.recipient).to.be.equal(recipient) + expect(eventBytes.data).to.be.equal(data) + }) + }) + }) }) diff --git a/cpp/test/utils/bytes-utils.js b/cpp/test/utils/bytes-utils.js index b33a3cf..9a46771 100644 --- a/cpp/test/utils/bytes-utils.js +++ b/cpp/test/utils/bytes-utils.js @@ -1,5 +1,5 @@ const R = require('ramda') -const { zeroPadValue } = require('ethers') +const { zeroPadValue, stripZerosLeft } = require('ethers') const getXbytesHex = (hex, offset, byteNum) => hex.slice(offset * 2, offset * 2 + byteNum * 2) @@ -49,6 +49,57 @@ const removeNullChars = string => string.replace(/\u0000/g, '') const bytes32 = _value => zeroPadValue(_0x(_value), 32) +const deserializeEventBytes = _eventBytes => { + offset = 0 + let nonce + try { + nonce = Number( + BigInt(stripZerosLeft(_0x(getXbytesHex(_eventBytes, offset, 32)))), + ) + } catch (e) { + nonce = 0 + } + offset += 32 + const token = Buffer.from( + no0x(stripZerosLeft(_0x(getXbytesHex(_eventBytes, offset, 32)))), + 'hex', + ).toString() + offset += 32 + const destinationChainid = stripZerosLeft( + _0x(getXbytesHex(_eventBytes, offset, 32)), + ) + offset += 32 + const amount = Number( + BigInt(stripZerosLeft(_0x(getXbytesHex(_eventBytes, offset, 32)))), + ) + offset += 32 + const sender = Buffer.from( + no0x(stripZerosLeft(_0x(getXbytesHex(_eventBytes, offset, 32)))), + 'hex', + ).toString() + offset += 32 + const recipientLen = Number( + BigInt(stripZerosLeft(_0x(getXbytesHex(_eventBytes, offset, 32)))), + ) + offset += 32 + const recipient = Buffer.from( + getXbytesHex(_eventBytes, offset, recipientLen), + 'hex', + ).toString() + offset += parseInt(recipientLen, 16) + const data = _eventBytes.slice(offset * 2, _eventBytes.length) + + return { + nonce, + token, + destinationChainid, + amount, + sender, + recipient, + data, + } +} + module.exports = { _0x, no0x, @@ -57,4 +108,5 @@ module.exports = { hexToString, hexStringToBytes, removeNullChars, + deserializeEventBytes, } From e1ebb1cfbda5bf01a991c040571063a1f4e0118a Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 18:27:38 +0100 Subject: [PATCH 14/23] fix(pam.test.js): errors --- cpp/test/pam.test.js | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/cpp/test/pam.test.js b/cpp/test/pam.test.js index bedfc9e..3383759 100644 --- a/cpp/test/pam.test.js +++ b/cpp/test/pam.test.js @@ -5,11 +5,11 @@ const { Protocols, Versions, } = require('@pnetwork/event-attestator') -const { zeroPadValue } = require('ethers') const { deploy, errors, + bytes32, getMetadataSample, getOperationSample, hexStringToBytes, @@ -45,12 +45,9 @@ describe('PAM testing', () => { const publicKey = hexToPublicKey(ea.signingKey.compressedPublicKey) - const evmEmitter = hexStringToBytes( - zeroPadValue('0x5623D0aF4bfb6F7B18d6618C166d518E4357ceE2', 32), - ) - const evmTopic0 = hexStringToBytes( - '0x66756e6473206172652073616675207361667520736166752073616675202e2e', - ) + const evmEmitter = '0x5623D0aF4bfb6F7B18d6618C166d518E4357ceE2' + const evmTopic0 = + '0x66756e6473206172652073616675207361667520736166752073616675202e2e' const recipient = 'recipient' @@ -71,7 +68,7 @@ describe('PAM testing', () => { blockHash: operation.blockId, transactionHash: operation.txId, address: evmEmitter, - topics: [evmTopic0, zeroPadValue('0x', 32)], + topics: [evmTopic0], data, } @@ -126,8 +123,8 @@ describe('PAM testing', () => { it('Should reject if the signature is invalid', async () => { // We will correct these later - const wrongEmitter = no0x(zeroPadValue('0x010203', 32)) - const wrongTopic0 = no0x(zeroPadValue('0x010203', 32)) + const wrongEmitter = no0x(bytes32('0x010203')) + const wrongTopic0 = no0x(bytes32('0x010203')) await adapter.contract.actions .setorigin([operation.originChainId, wrongEmitter, wrongTopic0]) .send(active(adapter.account)) @@ -154,12 +151,14 @@ describe('PAM testing', () => { }) it('Should reject if the topic zero is different', async () => { - // Should set the correct emitter - - const wrongTopic0 = no0x(zeroPadValue('0x010203', 32)) + const wrongTopic0 = no0x(bytes32('0x010203')) await adapter.contract.actions - .setorigin([operation.originChainId, evmEmitter, wrongTopic0]) + .setorigin([ + operation.originChainId, + no0x(bytes32(evmEmitter)), + wrongTopic0, + ]) .send(active(adapter.account)) const action = pam.contract.actions @@ -170,7 +169,11 @@ describe('PAM testing', () => { // Should set the correct topic zero await adapter.contract.actions - .setorigin([operation.originChainId, evmEmitter, evmTopic0]) + .setorigin([ + operation.originChainId, + no0x(bytes32(evmEmitter)), + no0x(bytes32(evmTopic0)), + ]) .send(active(adapter.account)) }) @@ -191,7 +194,7 @@ describe('PAM testing', () => { it('Should reject if token address does not match', async () => { const wrongOperation = { ...operation, - token: no0x(zeroPadValue('0x01', 32)), + token: no0x(bytes32('0x01')), } const action = pam.contract.actions @@ -204,7 +207,7 @@ describe('PAM testing', () => { it('Should reject if the destination chain id does not match', async () => { const wrongOperation = { ...operation, - destinationChainId: no0x(zeroPadValue('0x01', 32)), + destinationChainId: no0x(bytes32('0x01')), } const action = pam.contract.actions .isauthorized([wrongOperation, metadata]) @@ -229,7 +232,7 @@ describe('PAM testing', () => { it('Should reject when the sender is different', async () => { const wrongOperation = { ...operation, - sender: no0x(zeroPadValue('0x01', 32)), + sender: no0x(bytes32('0x01')), } action = pam.contract.actions @@ -382,6 +385,7 @@ describe('PAM testing', () => { await pam.contract.actions .isauthorized([operation2, metadata2]) .send(active(user)) + expect(pam.contract.bc.console).to.be.equal( '42cde5d898147a7bd21006e0fe541092151262cb2bde3a3244587e7993c473e0', ) From 9ad3ecbec97a660e5228a33c708a6728b950a5b2 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Fri, 8 Nov 2024 19:37:42 +0100 Subject: [PATCH 15/23] fix(lockbox.test.js): errors due previous changes --- cpp/test/lockbox.test.js | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/cpp/test/lockbox.test.js b/cpp/test/lockbox.test.js index e0671d2..22899f3 100644 --- a/cpp/test/lockbox.test.js +++ b/cpp/test/lockbox.test.js @@ -13,19 +13,24 @@ const { substract } = require('./utils/wharfkit-ext') const { getAccountsBalances } = require('./utils/get-token-balance') describe('Lockbox testing', () => { + const decimals = 4 const symbol = 'TKN' - const precision4 = precision(4) - const maxSupply = '500000000.0000' + const xsymbol = `X${symbol}` + const symbolPrecision = precision(decimals, symbol) + const xsymbolPrecision = precision(decimals, xsymbol) + const maxSupply = 500000000 + const token = { symbol: symbol, account: `${symbol.toLowerCase()}.token`, - maxSupply: `${maxSupply} ${symbol}`, + maxSupply: Asset.from(maxSupply, symbolPrecision), contract: undefined, } + const xerc20 = { - symbol: `X${symbol}`, - account: `x${symbol.toLowerCase()}.token`, - maxSupply: `${maxSupply} X${symbol}`, + symbol: xsymbol, + account: `${xsymbol.toLowerCase()}.token`, + maxSupply: Asset.from(maxSupply, xsymbolPrecision), contract: undefined, } @@ -66,9 +71,9 @@ describe('Lockbox testing', () => { const action = lockbox.contract.actions .create([ xerc20.account, - precision4(xerc20.symbol), + xsymbolPrecision, token.account, - precision4(token.symbol), + symbolPrecision, ]) .send(active(evil)) @@ -79,9 +84,9 @@ describe('Lockbox testing', () => { const action = lockbox.contract.actions .create([ xerc20.account, - precision4(xerc20.symbol), + xsymbolPrecision, token.account, - precision4(token.symbol), + symbolPrecision, ]) .send(active(lockbox.account)) @@ -94,9 +99,9 @@ describe('Lockbox testing', () => { const action = lockbox.contract.actions .create([ xerc20.account, - precision4(xerc20.symbol), + xsymbolPrecision, token.account, - precision4(token.symbol), + symbolPrecision, ]) .send(active(lockbox.account)) @@ -109,9 +114,9 @@ describe('Lockbox testing', () => { await lockbox.contract.actions .create([ xerc20.account, - precision4(xerc20.symbol), + xsymbolPrecision, token.account, - precision4(token.symbol), + symbolPrecision, ]) .send(active(lockbox.account)) @@ -121,9 +126,9 @@ describe('Lockbox testing', () => { expect(after).to.be.deep.equal({ token: token.account, - token_symbol: precision4(token.symbol), + token_symbol: symbolPrecision, xerc20: xerc20.account, - xerc20_symbol: precision4(xerc20.symbol), + xerc20_symbol: xsymbolPrecision, }) }) }) From fce772ea43ecbf739626741c7f5aeec82a08f3b1 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Sat, 9 Nov 2024 17:33:27 +0100 Subject: [PATCH 16/23] fix(adapter.cpp): userdata handling --- cpp/contracts/adapter.cpp | 34 ++++++++++++++++++++-------------- cpp/contracts/adapter.hpp | 7 +++++-- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/cpp/contracts/adapter.cpp b/cpp/contracts/adapter.cpp index 057b822..818479f 100644 --- a/cpp/contracts/adapter.cpp +++ b/cpp/contracts/adapter.cpp @@ -86,7 +86,6 @@ void adapter::setfeemanagr(const name& fee_manager) { void adapter::extract_memo_args( const name& self, - const name& userdata_owner, const string& memo, string& out_sender, string& out_dest_chainid, @@ -102,20 +101,23 @@ void adapter::extract_memo_args( out_sender = parts[0]; out_dest_chainid = parts[1].substr(2); out_recipient = parts[2]; - uint64_t userdata_id = stoull(parts[3]); + uint8_t has_userdata = stoi(parts[3]); check(out_sender.length() > 0, "invalid sender address"); check(out_recipient.length() > 0, "invalid destination address"); check(out_dest_chainid.length() == 64, "chain id must be a 32 bytes hex-string"); + auto sender_account = name(out_sender); + check(is_account(sender_account), "invalid sender account"); - if (userdata_id > 0) { - user_data table(userdata_owner, userdata_owner.value); - auto row = table.find(userdata_id); + if (has_userdata > 0) { + user_data table(self, sender_account.value); - check(row != table.end(), "userdata record not found"); + check(table.begin() != table.end(), "userdata record not found"); - out_data = row->payload; + auto row = *(table.begin()); + out_data = row.payload; + table.erase(table.begin()); } } @@ -123,18 +125,21 @@ void adapter::adduserdata(const name& caller, bytes payload) { require_auth(caller); check(payload.size() > 0, "invalid payload"); - user_data table(caller, caller.value); + user_data table(get_self(), caller.value); + // NOTE: we use a single record table here + // instead of a singleton because we can + // scope it by account value. + // Singletons doesn't support scoping and they + // are global througout the contract if (table.begin() == table.end()) { table.emplace(caller, [&](auto& r) { - // NOTE: an id == 0 means no userdata, check extract_memo_args - // for details r.id = 1; r.payload = payload; }); } else { - table.emplace(caller, [&](auto& r) { - r.id = table.end()->id + 1; + table.modify(table.begin(), caller, [&](auto& r) { + r.id = 1; r.payload = payload; }); } @@ -277,7 +282,7 @@ void adapter::token_transfer_from_user( void adapter::xerc20_transfer_from_any( const name& self, - const name& caller, + const name& from, const name& token, const name& xerc20, const asset& quantity, @@ -306,7 +311,7 @@ void adapter::xerc20_transfer_from_any( string recipient; bytes userdata; - extract_memo_args(self, caller, memo, sender, dest_chainid, recipient, userdata); + extract_memo_args(self, memo, sender, dest_chainid, recipient, userdata); auto recipient_bytes = to_bytes(recipient); @@ -352,6 +357,7 @@ void adapter::ontransfer(const name& from, const name& to, const asset& quantity if (is_token_transfer) check(quantity.symbol == token_symbol, "invalid token quantity symbol"); if (is_xerc20_transfer) check(quantity.symbol == xerc20_symbol, "invalid xerc20 quantity symbol"); + if (is_token_transfer) { lockbox_singleton _lockbox(xerc20, xerc20.value); check(_lockbox.exists(), "lockbox is not set for the underlying token"); diff --git a/cpp/contracts/adapter.hpp b/cpp/contracts/adapter.hpp index d0a15e8..cd75ff2 100644 --- a/cpp/contracts/adapter.hpp +++ b/cpp/contracts/adapter.hpp @@ -101,21 +101,23 @@ namespace eosio { }; void check_symbol_is_valid(const name& account, const symbol& sym); + void extract_memo_args( const name& self, - const name& userdata_owner, const string& memo, string& out_sender, string& out_dest_chainid, string& out_recipient, bytes& out_dat ); + void token_transfer_from_lockbox( const name& self, const name& token, const asset& quantity, const string& memo ); + void token_transfer_from_user( const name& self, const name& token, @@ -123,9 +125,10 @@ namespace eosio { const asset& quantity, const string& memo ); + void xerc20_transfer_from_any( const name& self, - const name& caller, + const name& from, const name& token, const name& xerc20, const asset& quantity, From 2fd78bcdd44c1d162acebddc4e7f8efbaf0ee572 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Sat, 9 Nov 2024 17:34:19 +0100 Subject: [PATCH 17/23] chore(bytes-utils.js): rm `parseInt` --- cpp/test/utils/bytes-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/test/utils/bytes-utils.js b/cpp/test/utils/bytes-utils.js index 9a46771..6628992 100644 --- a/cpp/test/utils/bytes-utils.js +++ b/cpp/test/utils/bytes-utils.js @@ -86,7 +86,7 @@ const deserializeEventBytes = _eventBytes => { getXbytesHex(_eventBytes, offset, recipientLen), 'hex', ).toString() - offset += parseInt(recipientLen, 16) + offset += recipientLen const data = _eventBytes.slice(offset * 2, _eventBytes.length) return { From 2669232a4f4f192b5d40ac25331010300de9b277 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Sat, 9 Nov 2024 17:34:56 +0100 Subject: [PATCH 18/23] chore(adapter-local.test.js): add userdata test --- cpp/test/utils/errors.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cpp/test/utils/errors.js b/cpp/test/utils/errors.js index 5073ae1..880b811 100644 --- a/cpp/test/utils/errors.js +++ b/cpp/test/utils/errors.js @@ -65,6 +65,10 @@ const INVALID_SYMBOL = eosio_assert('invalid symbol') const EXPECTED_32_BYTES = _thing => eosio_assert(`expected 32 bytes ${_thing}`) +const USER_DATA_RECORD_NOT_FOUND = eosio_assert('userdata record not found') + +const INVALID_PAYLOAD = eosio_assert('invalid payload') + module.exports = { AUTH_MISSING, SYMBOL_NOT_FOUND, @@ -97,4 +101,6 @@ module.exports = { ACCOUNT_STR_IS_TOO_LONG, EXPECTED_32_BYTES, INVALID_SYMBOL, + USER_DATA_RECORD_NOT_FOUND, + INVALID_PAYLOAD, } From 09f1519b300adcc68145f4f6c1f4675fd4ad09cd Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Sat, 9 Nov 2024 21:59:18 +0100 Subject: [PATCH 19/23] fix(adapter-non-local.test.js): after rebase changes --- cpp/test/adapter-non-local.test.js | 30 ++++++++++++++++---------- cpp/test/utils/get-operation-sample.js | 11 +++------- cpp/test/utils/get-token-balance.js | 6 ++++-- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/cpp/test/adapter-non-local.test.js b/cpp/test/adapter-non-local.test.js index d956a46..8979149 100644 --- a/cpp/test/adapter-non-local.test.js +++ b/cpp/test/adapter-non-local.test.js @@ -1,4 +1,5 @@ const { expect } = require('chai') +const { parseEther } = require('ethers') const { Blockchain, expectToThrow } = require('@eosnetwork/vert') const { Asset } = require('@wharfkit/antelope') const { Symbol } = Asset @@ -22,15 +23,17 @@ const { Protocols, ProofcastEventAttestator, } = require('@pnetwork/event-attestator') +const { adjustPrecision } = require('./utils/precision-utils') describe('Adapter Testing - Non Local Deployment', () => { const symbol = 'TST' const xsymbol = `X${symbol}` const maxSupply = 500000000 const minFee = 0.0018 - const precision = 8 - const symbolPrecision = Symbol.fromParts(symbol, precision) - const xsymbolPrecision = Symbol.fromParts(xsymbol, precision) + const symbolDecimals = 18 + const xsymbolDecimals = 8 + const symbolPrecision = Symbol.fromParts(symbol, symbolDecimals) + const xsymbolPrecision = Symbol.fromParts(xsymbol, xsymbolDecimals) const blockchain = new Blockchain() @@ -50,13 +53,15 @@ describe('Adapter Testing - Non Local Deployment', () => { const token = { symbol, + decimals: symbolDecimals, account: '', maxSupply: Asset.from(0, symbolPrecision), bytes: '000000000000000000000000810090f35dfa6b18b5eb59d298e2a2443a2811e2', // EVM address } const xerc20 = { - symbol: `${xsymbol}`, + symbol: xsymbol, + decimals: xsymbolDecimals, account: `${xsymbol.toLowerCase()}.token`, maxSupply: Asset.from(maxSupply, xsymbolPrecision), minFee: Asset.from(minFee, xsymbolPrecision), @@ -140,7 +145,7 @@ describe('Adapter Testing - Non Local Deployment', () => { token: '0x810090f35dfa6b18b5eb59d298e2a2443a2811e2', originChainId: evmOriginChainId, destinationChainId: Chains(Protocols.Eos).Mainnet, - amount: Asset.from(evmSwapAmount, xsymbolPrecision), + amount: Number(parseEther(String(evmSwapAmount))), sender: '000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266', recipient, @@ -226,12 +231,12 @@ describe('Adapter Testing - Non Local Deployment', () => { [xerc20], ) - expect(after[recipient][xerc20.symbol]).to.be.equal( - operation.amount.toString(), + expect(after[recipient][xerc20.symbol]).to.be.deep.equal( + Asset.from(evmSwapAmount, xsymbolPrecision).toString(), ) - expect(after[adapter.account][xerc20.symbol]).to.be.equal( - `0.0000 ${xerc20.symbol}`, + expect(after[adapter.account][xerc20.symbol]).to.be.deep.equal( + Asset.from(0, xsymbolPrecision).toString(), ) expect(before[feemanager][xerc20.symbol]).to.be.equal( @@ -272,11 +277,14 @@ describe('Adapter Testing - Non Local Deployment', () => { ) expect(after[recipient][xerc20.symbol]).to.equal( - sum(operation.amount, beforeAsset).toString(), + sum( + Asset.from(evmSwapAmount, xsymbolPrecision), + beforeAsset, + ).toString(), ) expect(after[adapter.account][xerc20.symbol]).to.be.equal( - `0.0000 ${xerc20.symbol}`, + Asset.from(0, xsymbolPrecision).toString(), ) }) }) diff --git a/cpp/test/utils/get-operation-sample.js b/cpp/test/utils/get-operation-sample.js index c8a4a11..83c8a84 100644 --- a/cpp/test/utils/get-operation-sample.js +++ b/cpp/test/utils/get-operation-sample.js @@ -1,6 +1,6 @@ const R = require('ramda') const { toBeHex, concat, stripZerosLeft } = require('ethers') -const { Asset } = require('@wharfkit/antelope') +const { Asset, UInt128 } = require('@wharfkit/antelope') const { _0x, no0x, bytes32 } = require('./bytes-utils') const { Protocols, Chains } = require('@pnetwork/event-attestator') const { getSymbolCodeRaw } = require('./eos-ext') @@ -35,9 +35,7 @@ const getOperation = _obj => { : bytes32(_0x(_obj.token)) const destinationChainId = bytes32(_0x(_obj.destinationChainId)) - const amount = isEosChain(_obj.destinationChainId) - ? Asset.from(_obj.amount) - : amount + const amount = UInt128.from(_obj.amount.toString()) const sender = isEosChain(_obj.originChainId) ? bytes32(_0x(Buffer.from(_obj.sender, 'utf-8').toString('hex'))) @@ -60,10 +58,7 @@ const getOperation = _obj => { } const serializeOperation = _operation => { - const amount = isEosChain(_operation.destinationChainId) - ? bytes32(toBeHex(BigInt(_operation.amount.value * 1e18))) // we need to include precision here - : bytes32(toBeHex(BigInt(_operation.amount))) // we expect amount to be already normalized - + const amount = bytes32(toBeHex(BigInt(_operation.amount.toString()))) const recipientLen = bytes32( toBeHex(BigInt(no0x(_operation.recipient).length)), ) diff --git a/cpp/test/utils/get-token-balance.js b/cpp/test/utils/get-token-balance.js index cbec171..c286ad0 100644 --- a/cpp/test/utils/get-token-balance.js +++ b/cpp/test/utils/get-token-balance.js @@ -21,6 +21,7 @@ const getAccountsBalances = (_accounts, _tokensAndSymbol) => { token.contract, account, token.symbol, + token.decimals, ).toString() } } @@ -34,8 +35,9 @@ const getTokenBalance = (contract, account, symcode, precision = 4) => { const scope = Name.from(account).value.value const primary_key = Asset.SymbolCode.from(symcode).value.value const row = contract.tables.accounts(scope).getTableRow(primary_key) - if (!row) return Asset.from(`0.${''.padEnd(precision, '0')} ${symcode}`) - return Asset.from(row.balance) + const symbolPrecision = Asset.Symbol.fromParts(symcode, precision) + if (!row) return Asset.from(0, symbolPrecision) + return Asset.from(row.balance, symbolPrecision) } module.exports = { From ae1ac02cb9565ab0b39055ff926a01e26ead0a6f Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Sat, 9 Nov 2024 22:11:22 +0100 Subject: [PATCH 20/23] fix(adapter.test.js): after rebase --- cpp/contracts/adapter.cpp | 5 ++--- cpp/test/adapter.test.js | 9 ++++----- cpp/test/utils/errors.js | 5 +++++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/cpp/contracts/adapter.cpp b/cpp/contracts/adapter.cpp index 818479f..a855d18 100644 --- a/cpp/contracts/adapter.cpp +++ b/cpp/contracts/adapter.cpp @@ -40,11 +40,10 @@ void adapter::create( ) { require_auth(get_self()); registry_adapter _registry(get_self(), get_self().value); - check(!_registry.exists(), "adapter already initialized"); + check(!_registry.exists(), "contract already initialized"); auto _token_bytes = token_bytes.extract_as_byte_array(); - check(is_account(xerc20), "xERC20 account does not exist"); - check(min_fee.symbol == xerc20_symbol, "invalid minimum fee symbol"); + check(is_account(xerc20), "invalid account"); check_symbol_is_valid(xerc20, xerc20_symbol); check(min_fee.symbol == xerc20_symbol, "invalid minimum fee symbol"); diff --git a/cpp/test/adapter.test.js b/cpp/test/adapter.test.js index 5e43fdf..c14fc40 100644 --- a/cpp/test/adapter.test.js +++ b/cpp/test/adapter.test.js @@ -22,6 +22,7 @@ const { } = require('@pnetwork/event-attestator') const TABLE_STORAGE = 'storage' +const TABLE_TEE = 'tee' describe('Adapter tests', () => { const symbol = 'TST' @@ -272,11 +273,9 @@ describe('Adapter tests', () => { ]) .send(active(adapter.account)) - const row = adapter.contract.tables - .regadapter(getAccountCodeRaw(adapter.account)) - .getTableRow(getSymbolCodeRaw(token.maxSupply)) + const row = getSingletonInstance(adapter.contract, 'regadapter') const storage = getSingletonInstance(adapter.contract, TABLE_STORAGE) - const tee = getSingletonInstance(adapter.contract, 'tee') + const tee = getSingletonInstance(adapter.contract, TABLE_TEE) const mappingsRow = adapter.contract.tables .mappings(getAccountCodeRaw(adapter.account)) .getTableRows() @@ -309,7 +308,7 @@ describe('Adapter tests', () => { ]) .send(active(adapter.account)) - await expectToThrow(action, 'eosio_assert: token already registered') + await expectToThrow(action, errors.CONTRACT_ALREADY_INITIALIZED) }) }) diff --git a/cpp/test/utils/errors.js b/cpp/test/utils/errors.js index 880b811..08642c3 100644 --- a/cpp/test/utils/errors.js +++ b/cpp/test/utils/errors.js @@ -69,6 +69,10 @@ const USER_DATA_RECORD_NOT_FOUND = eosio_assert('userdata record not found') const INVALID_PAYLOAD = eosio_assert('invalid payload') +const CONTRACT_ALREADY_INITIALIZED = eosio_assert( + 'contract already initialized', +) + module.exports = { AUTH_MISSING, SYMBOL_NOT_FOUND, @@ -103,4 +107,5 @@ module.exports = { INVALID_SYMBOL, USER_DATA_RECORD_NOT_FOUND, INVALID_PAYLOAD, + CONTRACT_ALREADY_INITIALIZED, } From 3406fc468e1736dc8cff4bd901ded5e4b47ff4a6 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Sat, 9 Nov 2024 23:28:53 +0100 Subject: [PATCH 21/23] fix(pam.test.js): after rebase --- cpp/test/pam.test.js | 126 +++++++++--------- ...to-pubkey.js => from-ethers-public-key.js} | 6 +- cpp/test/utils/get-operation-sample.js | 2 +- cpp/test/utils/index.js | 8 +- cpp/test/utils/wharfkit-ext.js | 7 - 5 files changed, 71 insertions(+), 78 deletions(-) rename cpp/test/utils/{hex-to-pubkey.js => from-ethers-public-key.js} (52%) diff --git a/cpp/test/pam.test.js b/cpp/test/pam.test.js index 3383759..f61a952 100644 --- a/cpp/test/pam.test.js +++ b/cpp/test/pam.test.js @@ -5,20 +5,21 @@ const { Protocols, Versions, } = require('@pnetwork/event-attestator') - const { + no0x, deploy, errors, bytes32, + getOperation, getMetadataSample, getOperationSample, - hexStringToBytes, - hexToPublicKey, - no0x, + fromEthersPublicKey, } = require('./utils') -const { active, getSingletonInstance } = require('./utils/eos-ext') const { expect } = require('chai') -const assert = require('assert') +const { parseEther } = require('ethers') +const { active, getSingletonInstance } = require('./utils/eos-ext') +const { serializeOperation } = require('./utils/get-operation-sample') +const { UInt128 } = require('@wharfkit/antelope') describe('PAM testing', () => { const user = 'user' @@ -43,8 +44,9 @@ describe('PAM testing', () => { privateKey, }) - const publicKey = hexToPublicKey(ea.signingKey.compressedPublicKey) + const publicKey = fromEthersPublicKey(ea.signingKey.compressedPublicKey) + const evmSwapAmount = 13 const evmEmitter = '0x5623D0aF4bfb6F7B18d6618C166d518E4357ceE2' const evmTopic0 = '0x66756e6473206172652073616675207361667520736166752073616675202e2e' @@ -53,17 +55,25 @@ describe('PAM testing', () => { const attestation = [] const blockchain = new Blockchain() - const eosChainId = - 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906' - const operation = getOperationSample({ - amount: '1337000000000000000000', - sender: '0000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf', - token: '000000000000000000000000f2e246bb76df876cef8b38ae84130f4f55de395b', - chainId: eosChainId, + + let operation = getOperation({ + blockId: + '0x21d41bf94358b9252115aee1eb250ef5a644e7fae776b3de508aacda5f4c26fc', + txId: '0x6be2de7375ad7c18fd5ca3ecc8b70e60c535750b042200070dc36f84175a16d6', + nonce: 0, + token: '0xf2e246bb76df876cef8b38ae84130f4f55de395b', + originChainId: Chains(Protocols.Evm).Mainnet, + destinationChainId: Chains(Protocols.Eos).Mainnet, + amount: Number(parseEther(String(evmSwapAmount))), + sender: '0x2b5ad5c4795c026514f8317c7a215e218dccd6cf', recipient, + data: '', }) - const data = - '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2e246bb76df876cef8b38ae84130f4f55de395baca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e9060000000000000000000000000000000000000000000000487a9a3045394400000000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf0000000000000000000000000000000000000000000000000000000000000009726563697069656e74' + + const data = serializeOperation(operation) + + operation = no0x(operation) + const event = { blockHash: operation.blockId, transactionHash: operation.txId, @@ -74,7 +84,7 @@ describe('PAM testing', () => { const signature = no0x(ea.formatEosSignature(ea.sign(event))) const preimage = no0x(ea.getEventPreImage(event)) - const metadata = getMetadataSample({ signature, preimage }) + const metadata = no0x(getMetadataSample({ signature, preimage })) before(async () => { blockchain.createAccounts(user, recipient) @@ -107,7 +117,7 @@ describe('PAM testing', () => { it('Should reject when the origin_chain_id is not set', async () => { const anotherEventAttestator = new ProofcastEventAttestator() - const anotherPublicKey = hexToPublicKey( + const anotherPublicKey = fromEthersPublicKey( anotherEventAttestator.signingKey.compressedPublicKey, ) await adapter.contract.actions @@ -261,8 +271,9 @@ describe('PAM testing', () => { ...operation, recipient: invalid, } + const data = - '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2e246bb76df876cef8b38ae84130f4f55de395baca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e9060000000000000000000000000000000000000000000000487a9a3045394400000000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf0000000000000000000000000000000000000000000000000000000000000010696e76616c6964726563697069656e74' + '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2e246bb76df876cef8b38ae84130f4f55de395baca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906000000000000000000000000000000000000000000000000b469471f801400000000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf0000000000000000000000000000000000000000000000000000000000000010696e76616c6964726563697069656e74' const newEvent = { ...event, data } const metadata = { preimage: no0x(ea.getEventPreImage(newEvent)), @@ -282,7 +293,7 @@ describe('PAM testing', () => { recipient: 'invalid', } const data = - '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2e246bb76df876cef8b38ae84130f4f55de395baca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e9060000000000000000000000000000000000000000000000487a9a3045394400000000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf0000000000000000000000000000000000000000000000000000000000000007696e76616c6964' + '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2e246bb76df876cef8b38ae84130f4f55de395baca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906000000000000000000000000000000000000000000000000b469471f801400000000000000000000000000002b5ad5c4795c026514f8317c7a215e218dccd6cf0000000000000000000000000000000000000000000000000000000000000007696e76616c6964' const newEvent = { ...event, data } const metadata = { preimage: no0x(ea.getEventPreImage(newEvent)), @@ -314,80 +325,71 @@ describe('PAM testing', () => { .send(active(user)) const expectedEventId = - '5677b05279928a1f054526d27f25bb081d1ef295738496dcf2a8f9507dc0bd7e' + '861a9d4acfb2eb75c8093e39ec0af1be4d20a789003c6ec9d2508b2ff1247843' expect(pam.contract.bc.console).to.be.equal(expectedEventId) }) }) it('Should authorize an EOSIO operation successfully', async () => { - const blockId = - '179ed57f474f446f2c9f6ea6702724cdad0cf26422299b368755ed93c0134a35' - const txId = - '27598a45ee610287d85695f823f8992c10602ce5bf3240ee20635219de4f734f' - const nonce = 0 - const token = - '0000000000000000000000000000000000000000000000746b6e2e746f6b656e' - const originChainId = no0x(Chains(Protocols.Eos).Jungle) - const destinationChainId = eosChainId - const amount = '9982500000000000000' - const sender = - '0000000000000000000000000000000000000000000000000000000075736572' - const recipient = 'recipient' - const data = '' - const operation2 = getOperationSample({ - blockId, - txId, - nonce, - token, - originChainId, - destinationChainId, - amount, - sender, - recipient, - data, + let eosOperation = getOperation({ + nonce: 0, + blockId: + '179ed57f474f446f2c9f6ea6702724cdad0cf26422299b368755ed93c0134a35', + txId: '27598a45ee610287d85695f823f8992c10602ce5bf3240ee20635219de4f734f', + token: '4,TKN', + originChainId: Chains(Protocols.Eos).Jungle, + destinationChainId: Chains(Protocols.Eos).Mainnet, + amount: 9982500000000000000, + sender: 'user', + recipient: 'recipient', + data: '', }) + const eventData = { + event_bytes: no0x(serializeOperation(eosOperation)), + } + + eosOperation = no0x(eosOperation) + const eosEmitter = Buffer.from('adapter') .toString('hex') .padStart(64, '0') + const eosTopic0 = Buffer.from('swap').toString('hex').padStart(64, '0') - const ea2 = new ProofcastEventAttestator({ + const eosEA = new ProofcastEventAttestator({ version: Versions.V1, protocolId: Protocols.Eos, chainId: Chains(Protocols.Eos).Jungle, privateKey, }) - const eventData = { - event_bytes: - '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746b6e2e746f6b656eaca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e9060000000000000000000000000000000000000000000000008a88f6dc4656400000000000000000000000000000000000000000000000000000000000757365720000000000000000000000000000000000000000000000000000000000000009726563697069656e74', - } - - const event2 = { - blockHash: operation2.blockId, - transactionHash: operation2.txId, + const eosEvent = { + blockHash: eosOperation.blockId, + transactionHash: eosOperation.txId, account: 'adapter', action: 'swap', data: eventData, } await adapter.contract.actions - .setorigin([operation2.originChainId, eosEmitter, eosTopic0]) + .setorigin([eosOperation.originChainId, eosEmitter, eosTopic0]) .send(active(adapter.account)) - const metadata2 = getMetadataSample({ - signature: no0x(ea2.formatEosSignature(ea2.sign(event2))), - preimage: no0x(ea2.getEventPreImage(event2)), - }) + const eosMetadata = no0x( + getMetadataSample({ + signature: no0x(eosEA.formatEosSignature(eosEA.sign(eosEvent))), + preimage: no0x(eosEA.getEventPreImage(eosEvent)), + }), + ) await pam.contract.actions - .isauthorized([operation2, metadata2]) + .isauthorized([eosOperation, eosMetadata]) .send(active(user)) expect(pam.contract.bc.console).to.be.equal( - '42cde5d898147a7bd21006e0fe541092151262cb2bde3a3244587e7993c473e0', + 'ff57d8865411fe30f3a29259c17ad16f613bf8fc4ec18269a4ea6c991448d2b6', ) }) }) diff --git a/cpp/test/utils/hex-to-pubkey.js b/cpp/test/utils/from-ethers-public-key.js similarity index 52% rename from cpp/test/utils/hex-to-pubkey.js rename to cpp/test/utils/from-ethers-public-key.js index 630f314..14d4c4f 100644 --- a/cpp/test/utils/hex-to-pubkey.js +++ b/cpp/test/utils/from-ethers-public-key.js @@ -1,8 +1,8 @@ -const { PublicKey } = require('@wharfkit/antelope') const { no0x } = require('./bytes-utils') +const { PublicKey } = require('@wharfkit/antelope') -module.exports.hexToPublicKey = _hex => +module.exports.fromEthersPublicKey = _compressed => PublicKey.from({ type: 'K1', - compressed: Uint8Array.from(Buffer.from(no0x(_hex), 'hex')), + compressed: Uint8Array.from(Buffer.from(no0x(_compressed), 'hex')), }) diff --git a/cpp/test/utils/get-operation-sample.js b/cpp/test/utils/get-operation-sample.js index 83c8a84..9d8aa8c 100644 --- a/cpp/test/utils/get-operation-sample.js +++ b/cpp/test/utils/get-operation-sample.js @@ -31,7 +31,7 @@ const getOperation = _obj => { const nonce = _obj.nonce const originChainId = bytes32(_0x(_obj.originChainId)) const token = isEosChain(_obj.originChainId) - ? bytes32(toBeHex(getSymbolCodeRaw(_obj.token))) + ? bytes32(toBeHex(_0x(String(getSymbolCodeRaw(_obj.token))))) : bytes32(_0x(_obj.token)) const destinationChainId = bytes32(_0x(_obj.destinationChainId)) diff --git a/cpp/test/utils/index.js b/cpp/test/utils/index.js index 5c83414..3f348cf 100644 --- a/cpp/test/utils/index.js +++ b/cpp/test/utils/index.js @@ -3,25 +3,23 @@ const errors = require('./errors.js') const eosExt = require('./eos-ext.js') const bytesUtils = require('./bytes-utils.js') const wharfkitExt = require('./wharfkit-ext.js') -const hexToPubkey = require('./hex-to-pubkey.js') -const hexToPublicKey = require('./hex-to-pubkey') const getSwapMemo = require('./get-swap-memo.js') const getEventBytes = require('./get-event-bytes.js') const getTokenBalance = require('./get-token-balance.js') const getMetadataSample = require('./get-metadata-sample.js') const getOperationSample = require('./get-operation-sample.js') +const fromEthersPublicKey = require('./from-ethers-public-key.js') module.exports = { + errors, ...deploy, ...eosExt, - errors, ...bytesUtils, - ...hexToPubkey, ...wharfkitExt, ...getSwapMemo, ...getEventBytes, - ...hexToPublicKey, ...getTokenBalance, ...getMetadataSample, ...getOperationSample, + ...fromEthersPublicKey, } diff --git a/cpp/test/utils/wharfkit-ext.js b/cpp/test/utils/wharfkit-ext.js index 96114fd..a3ea400 100644 --- a/cpp/test/utils/wharfkit-ext.js +++ b/cpp/test/utils/wharfkit-ext.js @@ -28,17 +28,10 @@ const divide = assetOperation(R.divide) const utf8HexString = _str => '0x' + Buffer.from(_str, 'utf-8').toString('hex') -const fromEthersPublicKey = _compressed => - PublicKey.from({ - type: 'K1', - compressed: Uint8Array.from(Buffer.from(no0x(_compressed), 'hex')), - }) - module.exports = { sum, divide, multiply, substract, utf8HexString, - fromEthersPublicKey, } From 029288fbe72e29f4977e6ce87b70d4784716012a Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Sat, 9 Nov 2024 23:54:13 +0100 Subject: [PATCH 22/23] chore(adapter-non-local.test.js): re-add precision truncation test --- cpp/test/adapter-non-local.test.js | 50 +++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/cpp/test/adapter-non-local.test.js b/cpp/test/adapter-non-local.test.js index 8979149..d5b3ced 100644 --- a/cpp/test/adapter-non-local.test.js +++ b/cpp/test/adapter-non-local.test.js @@ -287,6 +287,54 @@ describe('Adapter Testing - Non Local Deployment', () => { Asset.from(0, xsymbolPrecision).toString(), ) }) + + it('Should truncate the passed amount to the set precision', async () => { + const largeAmount = '1189215224969292133' + const largeOperation = { + ...operation, + amount: largeAmount, + } + + const largeEvent = { + ...event, + data: serializeOperation(largeOperation), + } + + const largeMetadata = { + preimage: evmEA.getEventPreImage(largeEvent), + signature: evmEA.formatEosSignature(evmEA.sign(largeEvent)), + } + + const before = getAccountsBalances( + [user, recipient, adapter.account], + [xerc20], + ) + + await adapter.contract.actions + .settle([user, no0x(largeOperation), no0x(largeMetadata)]) + .send(active(user)) + + const after = getAccountsBalances( + [user, recipient, adapter.account], + [xerc20], + ) + + const adjusted = Asset.from( + largeAmount / 10 ** symbolDecimals, + xsymbolPrecision, + ) + + expect( + substract( + after[recipient][xerc20.symbol], + before[recipient][xerc20.symbol], + ), + ).to.be.deep.equal(adjusted) + + expect(after[adapter.account][xerc20.symbol]).to.be.deep.equal( + Asset.from(0, xsymbolPrecision).toString(), + ) + }) }) describe('adapter::swap', () => { @@ -317,7 +365,7 @@ describe('Adapter Testing - Non Local Deployment', () => { ) const expectedEventBytes = - '0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000005158b1b8444260000000000000000000000000000000000000000000000000000000000075736572000000000000000000000000000000000000000000000000000000000000002a307865333936373537656337653661633763386535616265373238356464653437623938663232646238' + '0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000005158b1b8444260000000000000000000000000000000000000000000000000000000000075736572000000000000000000000000000000000000000000000000000000000000002a307865333936373537656337653661633763386535616265373238356464653437623938663232646238' expect(adapter.contract.bc.console).to.be.equal(expectedEventBytes) }) From fe6a69e285e4b0c7ae7d1f5628aa077c44d793c3 Mon Sep 17 00:00:00 2001 From: Mauro Piazza Date: Sat, 9 Nov 2024 23:56:53 +0100 Subject: [PATCH 23/23] chore(adater-local.test.js): remove unused imports --- cpp/test/adapter-local.test.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/cpp/test/adapter-local.test.js b/cpp/test/adapter-local.test.js index 8841983..5562084 100644 --- a/cpp/test/adapter-local.test.js +++ b/cpp/test/adapter-local.test.js @@ -1,12 +1,7 @@ const R = require('ramda') const { expect } = require('chai') const { Asset, Name } = require('@wharfkit/antelope') -const { - Blockchain, - expectToThrow, - nameToBigInt, - logExecutionTrace, -} = require('@eosnetwork/vert') +const { Blockchain, expectToThrow, nameToBigInt } = require('@eosnetwork/vert') const { deploy } = require('./utils/deploy') const { no0x, @@ -17,18 +12,11 @@ const { substract, getSwapMemo, prettyTrace, - hexToString, - getXbytesHex, - getEventBytes, - removeNullChars, getSymbolCodeRaw, getAccountsBalances, getSingletonInstance, - _0x, fromEthersPublicKey, deserializeEventBytes, - getAccountCodeRaw, - logExecutionTraces, } = require('./utils') const { toBeHex } = require('ethers')