From 51d49192c09deefc020079e94fd1a355a23359d2 Mon Sep 17 00:00:00 2001 From: Piero Bassa <64158778+pierobassa@users.noreply.github.com> Date: Tue, 9 Jan 2024 09:43:10 +0100 Subject: [PATCH] feat: transfer tokens clause builder (#445) * chore: workaround for unserializable bigint in tests by jest * feat: transferToken & transferVET * refactor: new error messaging format * feat: vip180 ABI * test: transferVET & transferToken cases * refactor: transfer tokens clause builder usage in examples * refactor: transfer tokens clause builder usage in transactions example * refactor: code duplication & minor * fix: update ABI * chore: code duplication exclusion for ABIs --- docs/contracts.md | 11 +- .../contracts/contract-create-ERC20-token.ts | 5 +- .../contract-transfer-ERC20-token.ts | 6 +- docs/examples/contracts/fixture.ts | 173 ------------ .../thor-client/delegated-transactions.ts | 12 +- docs/examples/thor-client/transactions.ts | 12 +- .../transactions/blockref_expiration.ts | 12 +- docs/examples/transactions/fee_delegation.ts | 11 +- .../examples/transactions/multiple_clauses.ts | 27 +- docs/examples/transactions/sign_decode.ts | 11 +- docs/examples/transactions/simulation.ts | 11 +- docs/examples/transactions/tx_dependency.ts | 20 +- docs/thor-client.md | 24 +- docs/transactions.md | 92 +++--- packages/core/jest.config.js | 3 +- packages/core/src/contract/clause.ts | 75 ++++- packages/core/src/contract/coder.ts | 6 +- packages/core/src/utils/const/abi.ts | 266 +++++++++++++++++- packages/core/tests/contract/contract.test.ts | 77 ++++- packages/core/tests/contract/fixture.ts | 195 ++++++++++++- sonar-project.properties | 1 + 21 files changed, 739 insertions(+), 311 deletions(-) diff --git a/docs/contracts.md b/docs/contracts.md index c73ef3330..5effbe53a 100644 --- a/docs/contracts.md +++ b/docs/contracts.md @@ -143,12 +143,11 @@ Once the contract is compiled, we can deploy it using the vechain SDK. The follo import { expect } from 'expect'; import { - erc20ContractABI, erc20ContractBytecode, privateKeyDeployer, thorSoloClient } from './fixture.js'; -import { addressUtils } from '@vechainfoundation/vechain-sdk-core'; +import { addressUtils, VIP180_ABI } from '@vechainfoundation/vechain-sdk-core'; // Deploying the ERC20 contract using the Thor client and the deployer's private key const transaction = await thorSoloClient.contracts.deployContract( @@ -167,7 +166,7 @@ expect(receipt.reverted).toEqual(false); // Executing a contract call to get the balance of the account that deployed the contract const balance = await thorSoloClient.contracts.executeContractCall( receipt.outputs[0].contractAddress, - erc20ContractABI, + VIP180_ABI, 'balanceOf', [addressUtils.fromPrivateKey(Buffer.from(privateKeyDeployer, 'hex'))] ); @@ -183,13 +182,13 @@ expect(parseInt(balance, 16)).toEqual(1e24); Once the contract is deployed, we can transfer tokens to another address using the vechain SDK. The following code shows how to transfer 10000 token smallest unit to another address: ```typescript { name=contract-transfer-erc20-token, category=example } +import { VIP180_ABI } from '@vechainfoundation/vechain-sdk-core'; import { - erc20ContractABI, privateKeyDeployer, setupERC20Contract, thorSoloClient } from './fixture.js'; -import { TransactionReceipt } from '@vechainfoundation/vechain-sdk-network'; +import { type TransactionReceipt } from '@vechainfoundation/vechain-sdk-network'; import { expect } from 'expect'; // Setting up the ERC20 contract and getting its address @@ -200,7 +199,7 @@ const transferResult = await thorSoloClient.contracts.executeContractTransaction( privateKeyDeployer, // Using deployer's private key to authorize the transaction contractAddress, // Contract address to which the transaction is sent - erc20ContractABI, // ABI of the ERC20 contract + VIP180_ABI, // ABI of the ERC20 contract 'transfer', // Name of the function to be executed in the contract ['0x9e7911de289c3c856ce7f421034f66b6cde49c39', 10000] // Arguments for the 'transfer' function: recipient address and amount ); diff --git a/docs/examples/contracts/contract-create-ERC20-token.ts b/docs/examples/contracts/contract-create-ERC20-token.ts index e061fe4e0..063bc2b1e 100644 --- a/docs/examples/contracts/contract-create-ERC20-token.ts +++ b/docs/examples/contracts/contract-create-ERC20-token.ts @@ -2,12 +2,11 @@ import { expect } from 'expect'; import { - erc20ContractABI, erc20ContractBytecode, privateKeyDeployer, thorSoloClient } from './fixture.js'; -import { addressUtils } from '@vechainfoundation/vechain-sdk-core'; +import { addressUtils, VIP180_ABI } from '@vechainfoundation/vechain-sdk-core'; // Deploying the ERC20 contract using the Thor client and the deployer's private key const transaction = await thorSoloClient.contracts.deployContract( @@ -26,7 +25,7 @@ expect(receipt.reverted).toEqual(false); // Executing a contract call to get the balance of the account that deployed the contract const balance = await thorSoloClient.contracts.executeContractCall( receipt.outputs[0].contractAddress, - erc20ContractABI, + VIP180_ABI, 'balanceOf', [addressUtils.fromPrivateKey(Buffer.from(privateKeyDeployer, 'hex'))] ); diff --git a/docs/examples/contracts/contract-transfer-ERC20-token.ts b/docs/examples/contracts/contract-transfer-ERC20-token.ts index f06a1ddb1..6c17cb987 100644 --- a/docs/examples/contracts/contract-transfer-ERC20-token.ts +++ b/docs/examples/contracts/contract-transfer-ERC20-token.ts @@ -1,10 +1,10 @@ +import { VIP180_ABI } from '@vechainfoundation/vechain-sdk-core'; import { - erc20ContractABI, privateKeyDeployer, setupERC20Contract, thorSoloClient } from './fixture.js'; -import { TransactionReceipt } from '@vechainfoundation/vechain-sdk-network'; +import { type TransactionReceipt } from '@vechainfoundation/vechain-sdk-network'; import { expect } from 'expect'; // Setting up the ERC20 contract and getting its address @@ -15,7 +15,7 @@ const transferResult = await thorSoloClient.contracts.executeContractTransaction( privateKeyDeployer, // Using deployer's private key to authorize the transaction contractAddress, // Contract address to which the transaction is sent - erc20ContractABI, // ABI of the ERC20 contract + VIP180_ABI, // ABI of the ERC20 contract 'transfer', // Name of the function to be executed in the contract ['0x9e7911de289c3c856ce7f421034f66b6cde49c39', 10000] // Arguments for the 'transfer' function: recipient address and amount ); diff --git a/docs/examples/contracts/fixture.ts b/docs/examples/contracts/fixture.ts index 5c768f7d3..b23c081b1 100644 --- a/docs/examples/contracts/fixture.ts +++ b/docs/examples/contracts/fixture.ts @@ -3,178 +3,6 @@ import { HttpClient, ThorClient } from '@vechainfoundation/vechain-sdk-network'; const erc20ContractBytecode: string = '0x60806040523480156200001157600080fd5b506040518060400160405280600b81526020017f53616d706c65546f6b656e0000000000000000000000000000000000000000008152506040518060400160405280600281526020017f535400000000000000000000000000000000000000000000000000000000000081525081600390816200008f91906200062c565b508060049081620000a191906200062c565b505050620000e633620000b9620000ec60201b60201c565b60ff16600a620000ca919062000896565b620f4240620000da9190620008e7565b620000f560201b60201c565b62000a3a565b60006012905090565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036200016a5760006040517fec442f0500000000000000000000000000000000000000000000000000000000815260040162000161919062000977565b60405180910390fd5b6200017e600083836200018260201b60201c565b5050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603620001d8578060026000828254620001cb919062000994565b92505081905550620002ae565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101562000267578381836040517fe450d38c0000000000000000000000000000000000000000000000000000000081526004016200025e93929190620009e0565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550505b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603620002f9578060026000828254039250508190555062000346565b806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051620003a5919062000a1d565b60405180910390a3505050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200043457607f821691505b6020821081036200044a5762000449620003ec565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620004b47fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000475565b620004c0868362000475565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b60006200050d620005076200050184620004d8565b620004e2565b620004d8565b9050919050565b6000819050919050565b6200052983620004ec565b62000541620005388262000514565b84845462000482565b825550505050565b600090565b6200055862000549565b620005658184846200051e565b505050565b5b818110156200058d57620005816000826200054e565b6001810190506200056b565b5050565b601f821115620005dc57620005a68162000450565b620005b18462000465565b81016020851015620005c1578190505b620005d9620005d08562000465565b8301826200056a565b50505b505050565b600082821c905092915050565b60006200060160001984600802620005e1565b1980831691505092915050565b60006200061c8383620005ee565b9150826002028217905092915050565b6200063782620003b2565b67ffffffffffffffff811115620006535762000652620003bd565b5b6200065f82546200041b565b6200066c82828562000591565b600060209050601f831160018114620006a457600084156200068f578287015190505b6200069b85826200060e565b8655506200070b565b601f198416620006b48662000450565b60005b82811015620006de57848901518255600182019150602085019450602081019050620006b7565b86831015620006fe5784890151620006fa601f891682620005ee565b8355505b6001600288020188555050505b505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008160011c9050919050565b6000808291508390505b6001851115620007a15780860481111562000779576200077862000713565b5b6001851615620007895780820291505b8081029050620007998562000742565b945062000759565b94509492505050565b600082620007bc57600190506200088f565b81620007cc57600090506200088f565b8160018114620007e55760028114620007f05762000826565b60019150506200088f565b60ff84111562000805576200080462000713565b5b8360020a9150848211156200081f576200081e62000713565b5b506200088f565b5060208310610133831016604e8410600b8410161715620008605782820a9050838111156200085a576200085962000713565b5b6200088f565b6200086f84848460016200074f565b9250905081840481111562000889576200088862000713565b5b81810290505b9392505050565b6000620008a382620004d8565b9150620008b083620004d8565b9250620008df7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8484620007aa565b905092915050565b6000620008f482620004d8565b91506200090183620004d8565b92508282026200091181620004d8565b915082820484148315176200092b576200092a62000713565b5b5092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006200095f8262000932565b9050919050565b620009718162000952565b82525050565b60006020820190506200098e600083018462000966565b92915050565b6000620009a182620004d8565b9150620009ae83620004d8565b9250828201905080821115620009c957620009c862000713565b5b92915050565b620009da81620004d8565b82525050565b6000606082019050620009f7600083018662000966565b62000a066020830185620009cf565b62000a156040830184620009cf565b949350505050565b600060208201905062000a346000830184620009cf565b92915050565b610e558062000a4a6000396000f3fe608060405234801561001057600080fd5b50600436106100935760003560e01c8063313ce56711610066578063313ce5671461013457806370a082311461015257806395d89b4114610182578063a9059cbb146101a0578063dd62ed3e146101d057610093565b806306fdde0314610098578063095ea7b3146100b657806318160ddd146100e657806323b872dd14610104575b600080fd5b6100a0610200565b6040516100ad9190610aa9565b60405180910390f35b6100d060048036038101906100cb9190610b64565b610292565b6040516100dd9190610bbf565b60405180910390f35b6100ee6102b5565b6040516100fb9190610be9565b60405180910390f35b61011e60048036038101906101199190610c04565b6102bf565b60405161012b9190610bbf565b60405180910390f35b61013c6102ee565b6040516101499190610c73565b60405180910390f35b61016c60048036038101906101679190610c8e565b6102f7565b6040516101799190610be9565b60405180910390f35b61018a61033f565b6040516101979190610aa9565b60405180910390f35b6101ba60048036038101906101b59190610b64565b6103d1565b6040516101c79190610bbf565b60405180910390f35b6101ea60048036038101906101e59190610cbb565b6103f4565b6040516101f79190610be9565b60405180910390f35b60606003805461020f90610d2a565b80601f016020809104026020016040519081016040528092919081815260200182805461023b90610d2a565b80156102885780601f1061025d57610100808354040283529160200191610288565b820191906000526020600020905b81548152906001019060200180831161026b57829003601f168201915b5050505050905090565b60008061029d61047b565b90506102aa818585610483565b600191505092915050565b6000600254905090565b6000806102ca61047b565b90506102d7858285610495565b6102e2858585610529565b60019150509392505050565b60006012905090565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60606004805461034e90610d2a565b80601f016020809104026020016040519081016040528092919081815260200182805461037a90610d2a565b80156103c75780601f1061039c576101008083540402835291602001916103c7565b820191906000526020600020905b8154815290600101906020018083116103aa57829003601f168201915b5050505050905090565b6000806103dc61047b565b90506103e9818585610529565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b610490838383600161061d565b505050565b60006104a184846103f4565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146105235781811015610513578281836040517ffb8f41b200000000000000000000000000000000000000000000000000000000815260040161050a93929190610d6a565b60405180910390fd5b6105228484848403600061061d565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361059b5760006040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016105929190610da1565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361060d5760006040517fec442f050000000000000000000000000000000000000000000000000000000081526004016106049190610da1565b60405180910390fd5b6106188383836107f4565b505050565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361068f5760006040517fe602df050000000000000000000000000000000000000000000000000000000081526004016106869190610da1565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036107015760006040517f94280d620000000000000000000000000000000000000000000000000000000081526004016106f89190610da1565b60405180910390fd5b81600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555080156107ee578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516107e59190610be9565b60405180910390a35b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361084657806002600082825461083a9190610deb565b92505081905550610919565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050818110156108d2578381836040517fe450d38c0000000000000000000000000000000000000000000000000000000081526004016108c993929190610d6a565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550505b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361096257806002600082825403925050819055506109af565b806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610a0c9190610be9565b60405180910390a3505050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610a53578082015181840152602081019050610a38565b60008484015250505050565b6000601f19601f8301169050919050565b6000610a7b82610a19565b610a858185610a24565b9350610a95818560208601610a35565b610a9e81610a5f565b840191505092915050565b60006020820190508181036000830152610ac38184610a70565b905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610afb82610ad0565b9050919050565b610b0b81610af0565b8114610b1657600080fd5b50565b600081359050610b2881610b02565b92915050565b6000819050919050565b610b4181610b2e565b8114610b4c57600080fd5b50565b600081359050610b5e81610b38565b92915050565b60008060408385031215610b7b57610b7a610acb565b5b6000610b8985828601610b19565b9250506020610b9a85828601610b4f565b9150509250929050565b60008115159050919050565b610bb981610ba4565b82525050565b6000602082019050610bd46000830184610bb0565b92915050565b610be381610b2e565b82525050565b6000602082019050610bfe6000830184610bda565b92915050565b600080600060608486031215610c1d57610c1c610acb565b5b6000610c2b86828701610b19565b9350506020610c3c86828701610b19565b9250506040610c4d86828701610b4f565b9150509250925092565b600060ff82169050919050565b610c6d81610c57565b82525050565b6000602082019050610c886000830184610c64565b92915050565b600060208284031215610ca457610ca3610acb565b5b6000610cb284828501610b19565b91505092915050565b60008060408385031215610cd257610cd1610acb565b5b6000610ce085828601610b19565b9250506020610cf185828601610b19565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680610d4257607f821691505b602082108103610d5557610d54610cfb565b5b50919050565b610d6481610af0565b82525050565b6000606082019050610d7f6000830186610d5b565b610d8c6020830185610bda565b610d996040830184610bda565b949350505050565b6000602082019050610db66000830184610d5b565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610df682610b2e565b9150610e0183610b2e565b9250828201905080821115610e1957610e18610dbc565b5b9291505056fea2646970667358221220912f5265edaea44910db734f0d00fccd257c78dba79c126931551eaad1a334f764736f6c63430008170033'; -const erc20ContractABI = [ - { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, - { - inputs: [ - { internalType: 'address', name: 'spender', type: 'address' }, - { internalType: 'uint256', name: 'allowance', type: 'uint256' }, - { internalType: 'uint256', name: 'needed', type: 'uint256' } - ], - name: 'ERC20InsufficientAllowance', - type: 'error' - }, - { - inputs: [ - { internalType: 'address', name: 'sender', type: 'address' }, - { internalType: 'uint256', name: 'balance', type: 'uint256' }, - { internalType: 'uint256', name: 'needed', type: 'uint256' } - ], - name: 'ERC20InsufficientBalance', - type: 'error' - }, - { - inputs: [ - { internalType: 'address', name: 'approver', type: 'address' } - ], - name: 'ERC20InvalidApprover', - type: 'error' - }, - { - inputs: [ - { internalType: 'address', name: 'receiver', type: 'address' } - ], - name: 'ERC20InvalidReceiver', - type: 'error' - }, - { - inputs: [{ internalType: 'address', name: 'sender', type: 'address' }], - name: 'ERC20InvalidSender', - type: 'error' - }, - { - inputs: [{ internalType: 'address', name: 'spender', type: 'address' }], - name: 'ERC20InvalidSpender', - type: 'error' - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'owner', - type: 'address' - }, - { - indexed: true, - internalType: 'address', - name: 'spender', - type: 'address' - }, - { - indexed: false, - internalType: 'uint256', - name: 'value', - type: 'uint256' - } - ], - name: 'Approval', - type: 'event' - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'from', - type: 'address' - }, - { - indexed: true, - internalType: 'address', - name: 'to', - type: 'address' - }, - { - indexed: false, - internalType: 'uint256', - name: 'value', - type: 'uint256' - } - ], - name: 'Transfer', - type: 'event' - }, - { - inputs: [ - { internalType: 'address', name: 'owner', type: 'address' }, - { internalType: 'address', name: 'spender', type: 'address' } - ], - name: 'allowance', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'spender', type: 'address' }, - { internalType: 'uint256', name: 'value', type: 'uint256' } - ], - name: 'approve', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [{ internalType: 'address', name: 'account', type: 'address' }], - name: 'balanceOf', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'decimals', - outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'name', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'symbol', - outputs: [{ internalType: 'string', name: '', type: 'string' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [], - name: 'totalSupply', - outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], - stateMutability: 'view', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'value', type: 'uint256' } - ], - name: 'transfer', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function' - }, - { - inputs: [ - { internalType: 'address', name: 'from', type: 'address' }, - { internalType: 'address', name: 'to', type: 'address' }, - { internalType: 'uint256', name: 'value', type: 'uint256' } - ], - name: 'transferFrom', - outputs: [{ internalType: 'bool', name: '', type: 'bool' }], - stateMutability: 'nonpayable', - type: 'function' - } -]; - // Defining the private key for the deployer account, which has VTHO for deployment costs const privateKeyDeployer = '706e6acd567fdc22db54aead12cb39db01c4832f149f95299aa8dd8bef7d28ff'; @@ -205,7 +33,6 @@ const setupERC20Contract = async () => { }; export { - erc20ContractABI, erc20ContractBytecode, privateKeyDeployer, thorSoloClient, diff --git a/docs/examples/thor-client/delegated-transactions.ts b/docs/examples/thor-client/delegated-transactions.ts index 87234fb09..7b44d3b2d 100644 --- a/docs/examples/thor-client/delegated-transactions.ts +++ b/docs/examples/thor-client/delegated-transactions.ts @@ -3,7 +3,8 @@ import { TransactionUtils, TransactionHandler, dataUtils, - unitsUtils + unitsUtils, + contract } from '@vechainfoundation/vechain-sdk-core'; import { HttpClient, ThorClient } from '@vechainfoundation/vechain-sdk-network'; import { expect } from 'expect'; @@ -21,11 +22,10 @@ const latestBlock = await thorSoloClient.blocks.getBestBlock(); // 3 - Create transaction clauses const clauses = [ - { - to: '0x9e7911de289c3c856ce7f421034f66b6cde49c39', - value: unitsUtils.parseVET('10000').toString(), // VET transfer transaction - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x9e7911de289c3c856ce7f421034f66b6cde49c39', + unitsUtils.parseVET('10000') + ) ]; // Get gas @NOTE this is an approximation diff --git a/docs/examples/thor-client/transactions.ts b/docs/examples/thor-client/transactions.ts index 79c9aae06..98bf597d4 100644 --- a/docs/examples/thor-client/transactions.ts +++ b/docs/examples/thor-client/transactions.ts @@ -3,7 +3,8 @@ import { TransactionUtils, TransactionHandler, dataUtils, - unitsUtils + unitsUtils, + contract } from '@vechainfoundation/vechain-sdk-core'; import { HttpClient, ThorClient } from '@vechainfoundation/vechain-sdk-network'; import { expect } from 'expect'; @@ -21,11 +22,10 @@ const latestBlock = await thorSoloClient.blocks.getBestBlock(); // 3 - Create clauses const clauses = [ - { - to: '0x9e7911de289c3c856ce7f421034f66b6cde49c39', - value: unitsUtils.parseVET('10000').toString(), // VET transfer transaction - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x9e7911de289c3c856ce7f421034f66b6cde49c39', + unitsUtils.parseVET('10000') + ) ]; // 4 - Create transaction diff --git a/docs/examples/transactions/blockref_expiration.ts b/docs/examples/transactions/blockref_expiration.ts index 3f71dd231..969a04b27 100644 --- a/docs/examples/transactions/blockref_expiration.ts +++ b/docs/examples/transactions/blockref_expiration.ts @@ -6,18 +6,18 @@ import { networkInfo, type TransactionClause, type TransactionBody, - unitsUtils + unitsUtils, + contract } from '@vechainfoundation/vechain-sdk-core'; import { expect } from 'expect'; // 1 - Define clauses const clauses: TransactionClause[] = [ - { - to: '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', - value: unitsUtils.parseVET('1000').toString(), // VET transfer transaction - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', + unitsUtils.parseVET('1000') + ) ]; // 2 - Define transaction body diff --git a/docs/examples/transactions/fee_delegation.ts b/docs/examples/transactions/fee_delegation.ts index 7ed0bc439..fc46fe680 100644 --- a/docs/examples/transactions/fee_delegation.ts +++ b/docs/examples/transactions/fee_delegation.ts @@ -1,4 +1,4 @@ -import { networkInfo } from '@vechainfoundation/vechain-sdk-core'; +import { contract, networkInfo } from '@vechainfoundation/vechain-sdk-core'; import { Transaction, secp256k1, @@ -15,11 +15,10 @@ import { expect } from 'expect'; // 1 - Define clause const clauses: TransactionClause[] = [ - { - to: '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', - value: unitsUtils.parseVET('10000').toString(), // VET transfer transaction - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', + unitsUtils.parseVET('10000') + ) ]; // 2 - Define transaction body diff --git a/docs/examples/transactions/multiple_clauses.ts b/docs/examples/transactions/multiple_clauses.ts index 55c66815a..01edcd993 100644 --- a/docs/examples/transactions/multiple_clauses.ts +++ b/docs/examples/transactions/multiple_clauses.ts @@ -1,4 +1,8 @@ -import { networkInfo } from '@vechainfoundation/vechain-sdk-core'; +import { + VTHO_ADDRESS, + contract, + networkInfo +} from '@vechainfoundation/vechain-sdk-core'; import { Transaction, secp256k1, @@ -13,18 +17,15 @@ import { expect } from 'expect'; // 1 - Define multiple clauses const clauses: TransactionClause[] = [ - { - to: '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', - value: unitsUtils.parseVET('10000').toString(), // VET transfer clause - data: '0x' - }, - { - to: '0x0000000000000000000000000000456E65726779', - value: 0, // Contract call to transfer VTHO - data: '0xa9059cbb0000000000000000000000007567d83b7b8d80addcb\ -281a71d54fc7b3364ffed0000000000000000000000000000000000000000\ -0000000000000000000003e8' - } + contract.clauseBuilder.transferVET( + '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', + unitsUtils.parseVET('10000') + ), + contract.clauseBuilder.transferToken( + VTHO_ADDRESS, + '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', + unitsUtils.parseUnits('10000', 18) // 10000 VTHO + ) ]; // 2 - Calculate intrinsic gas of both clauses diff --git a/docs/examples/transactions/sign_decode.ts b/docs/examples/transactions/sign_decode.ts index 8695d391b..48056bfca 100644 --- a/docs/examples/transactions/sign_decode.ts +++ b/docs/examples/transactions/sign_decode.ts @@ -1,4 +1,4 @@ -import { networkInfo } from '@vechainfoundation/vechain-sdk-core'; +import { contract, networkInfo } from '@vechainfoundation/vechain-sdk-core'; import { Transaction, secp256k1, @@ -13,11 +13,10 @@ import { expect } from 'expect'; // 1 - Define clauses const clauses: TransactionClause[] = [ - { - to: '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', - value: unitsUtils.parseVET('10000').toString(), - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', + unitsUtils.parseVET('10000') + ) ]; // 2 - Calculate intrinsic gas of clauses diff --git a/docs/examples/transactions/simulation.ts b/docs/examples/transactions/simulation.ts index e0375d3e0..0b6ddbee4 100644 --- a/docs/examples/transactions/simulation.ts +++ b/docs/examples/transactions/simulation.ts @@ -1,6 +1,6 @@ import { expect } from 'expect'; import { HttpClient, ThorClient } from '@vechainfoundation/vechain-sdk-network'; -import { unitsUtils } from '@vechainfoundation/vechain-sdk-core'; +import { contract, unitsUtils } from '@vechainfoundation/vechain-sdk-core'; // In this example we simulate a transaction of sending 1 VET to another account // And we demonstrate (1) how we can check the expected gas cost and (2) whether the transaction is successful @@ -13,11 +13,10 @@ const thorSoloClient = new ThorClient(soloNetwork); // 2(a) - create the transaction for a VET transfer const transaction1 = { clauses: [ - { - to: '0xb717b660cd51109334bd10b2c168986055f58c1a', - value: unitsUtils.parseVET('1').toString(), // converts from 1 VET to wei - data: '0x' - } + contract.clauseBuilder.transferVET( + '0xb717b660cd51109334bd10b2c168986055f58c1a', + unitsUtils.parseVET('1') + ) ], // Please note - this field one of the optional fields that may be passed (see SimulateTransactionOptions), // and is only required if you want to simulate a transaction diff --git a/docs/examples/transactions/tx_dependency.ts b/docs/examples/transactions/tx_dependency.ts index 0d0cd10ca..0c4cad836 100644 --- a/docs/examples/transactions/tx_dependency.ts +++ b/docs/examples/transactions/tx_dependency.ts @@ -1,4 +1,4 @@ -import { networkInfo } from '@vechainfoundation/vechain-sdk-core'; +import { contract, networkInfo } from '@vechainfoundation/vechain-sdk-core'; import { Transaction, secp256k1, @@ -13,18 +13,16 @@ import { expect } from 'expect'; // 1 - Define transaction clauses const txAClauses: TransactionClause[] = [ - { - to: '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', - value: unitsUtils.parseVET('1000').toString(), // VET transfer transaction - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', + unitsUtils.parseVET('1000') + ) ]; const txBClauses: TransactionClause[] = [ - { - to: '0x7ccadeea14dd6727845b58f8aa7aad0f41a002a2', - value: 2000, // VET transfer transaction - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x7ccadeea14dd6727845b58f8aa7aad0f41a002a2', + unitsUtils.parseVET('1') + ) ]; // 2 - Define transaction A with no dependencies diff --git a/docs/thor-client.md b/docs/thor-client.md index acef8719a..32991ae2b 100644 --- a/docs/thor-client.md +++ b/docs/thor-client.md @@ -326,7 +326,8 @@ import { TransactionUtils, TransactionHandler, dataUtils, - unitsUtils + unitsUtils, + contract } from '@vechainfoundation/vechain-sdk-core'; import { HttpClient, ThorClient } from '@vechainfoundation/vechain-sdk-network'; import { expect } from 'expect'; @@ -344,11 +345,10 @@ const latestBlock = await thorSoloClient.blocks.getBestBlock(); // 3 - Create clauses const clauses = [ - { - to: '0x9e7911de289c3c856ce7f421034f66b6cde49c39', - value: unitsUtils.parseVET('10000').toString(), // VET transfer transaction - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x9e7911de289c3c856ce7f421034f66b6cde49c39', + unitsUtils.parseVET('10000') + ) ]; // 4 - Create transaction @@ -429,7 +429,8 @@ import { TransactionUtils, TransactionHandler, dataUtils, - unitsUtils + unitsUtils, + contract } from '@vechainfoundation/vechain-sdk-core'; import { HttpClient, ThorClient } from '@vechainfoundation/vechain-sdk-network'; import { expect } from 'expect'; @@ -447,11 +448,10 @@ const latestBlock = await thorSoloClient.blocks.getBestBlock(); // 3 - Create transaction clauses const clauses = [ - { - to: '0x9e7911de289c3c856ce7f421034f66b6cde49c39', - value: unitsUtils.parseVET('10000').toString(), // VET transfer transaction - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x9e7911de289c3c856ce7f421034f66b6cde49c39', + unitsUtils.parseVET('10000') + ) ]; // Get gas @NOTE this is an approximation diff --git a/docs/transactions.md b/docs/transactions.md index 54cbe804c..416042ce6 100644 --- a/docs/transactions.md +++ b/docs/transactions.md @@ -16,7 +16,7 @@ To break it down: In this example a simple transaction with a single clause is created, signed, encoded and then decoded ```typescript { name=sign_decode, category=example } -import { networkInfo } from '@vechainfoundation/vechain-sdk-core'; +import { contract, networkInfo } from '@vechainfoundation/vechain-sdk-core'; import { Transaction, secp256k1, @@ -31,11 +31,10 @@ import { expect } from 'expect'; // 1 - Define clauses const clauses: TransactionClause[] = [ - { - to: '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', - value: unitsUtils.parseVET('10000').toString(), - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', + unitsUtils.parseVET('10000') + ) ]; // 2 - Calculate intrinsic gas of clauses @@ -80,7 +79,11 @@ In VechainThor blockchain a transaction can be composed of multiple clauses. \ Clauses allow to send multiple payloads to different recipients within a single transaction. ```typescript { name=multiple_clauses, category=example } -import { networkInfo } from '@vechainfoundation/vechain-sdk-core'; +import { + VTHO_ADDRESS, + contract, + networkInfo +} from '@vechainfoundation/vechain-sdk-core'; import { Transaction, secp256k1, @@ -95,18 +98,15 @@ import { expect } from 'expect'; // 1 - Define multiple clauses const clauses: TransactionClause[] = [ - { - to: '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', - value: unitsUtils.parseVET('10000').toString(), // VET transfer clause - data: '0x' - }, - { - to: '0x0000000000000000000000000000456E65726779', - value: 0, // Contract call to transfer VTHO - data: '0xa9059cbb0000000000000000000000007567d83b7b8d80addcb\ -281a71d54fc7b3364ffed0000000000000000000000000000000000000000\ -0000000000000000000003e8' - } + contract.clauseBuilder.transferVET( + '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', + unitsUtils.parseVET('10000') + ), + contract.clauseBuilder.transferToken( + VTHO_ADDRESS, + '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', + unitsUtils.parseUnits('10000', 18) // 10000 VTHO + ) ]; // 2 - Calculate intrinsic gas of both clauses @@ -149,7 +149,7 @@ expect(decodedTx.body.clauses.length).toBe(clauses.length); Fee delegation is a feature on the VechainThor blockchain which enables the transaction sender to request another entity, a sponsor, to pay for the transaction fee on the sender's behalf. ```typescript { name=fee_delegation, category=example } -import { networkInfo } from '@vechainfoundation/vechain-sdk-core'; +import { contract, networkInfo } from '@vechainfoundation/vechain-sdk-core'; import { Transaction, secp256k1, @@ -166,11 +166,10 @@ import { expect } from 'expect'; // 1 - Define clause const clauses: TransactionClause[] = [ - { - to: '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', - value: unitsUtils.parseVET('10000').toString(), // VET transfer transaction - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', + unitsUtils.parseVET('10000') + ) ]; // 2 - Define transaction body @@ -233,18 +232,18 @@ import { networkInfo, type TransactionClause, type TransactionBody, - unitsUtils + unitsUtils, + contract } from '@vechainfoundation/vechain-sdk-core'; import { expect } from 'expect'; // 1 - Define clauses const clauses: TransactionClause[] = [ - { - to: '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', - value: unitsUtils.parseVET('1000').toString(), // VET transfer transaction - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', + unitsUtils.parseVET('1000') + ) ]; // 2 - Define transaction body @@ -285,7 +284,7 @@ expect(decodedTx.body.expiration).toBe(body.expiration); A transaction can be set to only be processed after another transaction, therefore defining an execution order for transactions. The _DependsOn_ field is the Id of the transaction on which the current transaction depends on. If the transaction does not depend on others _DependsOn_ can be set to _null_ ```typescript { name=tx_dependency, category=example } -import { networkInfo } from '@vechainfoundation/vechain-sdk-core'; +import { contract, networkInfo } from '@vechainfoundation/vechain-sdk-core'; import { Transaction, secp256k1, @@ -300,18 +299,16 @@ import { expect } from 'expect'; // 1 - Define transaction clauses const txAClauses: TransactionClause[] = [ - { - to: '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', - value: unitsUtils.parseVET('1000').toString(), // VET transfer transaction - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x7567d83b7b8d80addcb281a71d54fc7b3364ffed', + unitsUtils.parseVET('1000') + ) ]; const txBClauses: TransactionClause[] = [ - { - to: '0x7ccadeea14dd6727845b58f8aa7aad0f41a002a2', - value: 2000, // VET transfer transaction - data: '0x' - } + contract.clauseBuilder.transferVET( + '0x7ccadeea14dd6727845b58f8aa7aad0f41a002a2', + unitsUtils.parseVET('1') + ) ]; // 2 - Define transaction A with no dependencies @@ -381,7 +378,7 @@ Note - the result of a transaction might be different depending on the state(blo ```typescript { name=simulation, category=example } import { expect } from 'expect'; import { HttpClient, ThorClient } from '@vechainfoundation/vechain-sdk-network'; -import { unitsUtils } from '@vechainfoundation/vechain-sdk-core'; +import { contract, unitsUtils } from '@vechainfoundation/vechain-sdk-core'; // In this example we simulate a transaction of sending 1 VET to another account // And we demonstrate (1) how we can check the expected gas cost and (2) whether the transaction is successful @@ -394,11 +391,10 @@ const thorSoloClient = new ThorClient(soloNetwork); // 2(a) - create the transaction for a VET transfer const transaction1 = { clauses: [ - { - to: '0xb717b660cd51109334bd10b2c168986055f58c1a', - value: unitsUtils.parseVET('1').toString(), // converts from 1 VET to wei - data: '0x' - } + contract.clauseBuilder.transferVET( + '0xb717b660cd51109334bd10b2c168986055f58c1a', + unitsUtils.parseVET('1') + ) ], // Please note - this field one of the optional fields that may be passed (see SimulateTransactionOptions), // and is only required if you want to simulate a transaction diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js index 3a06649ce..ed83ba061 100644 --- a/packages/core/jest.config.js +++ b/packages/core/jest.config.js @@ -4,5 +4,6 @@ module.exports = { testEnvironment: 'node', coverageReporters: ['html', 'lcov', 'json'], runner: 'groups', - reporters: ['default', 'jest-junit'] + reporters: ['default', 'jest-junit'], + workerThreads: true }; diff --git a/packages/core/src/contract/clause.ts b/packages/core/src/contract/clause.ts index 48e2a7f05..cf818bb01 100644 --- a/packages/core/src/contract/clause.ts +++ b/packages/core/src/contract/clause.ts @@ -2,6 +2,12 @@ import { type InterfaceAbi } from 'ethers'; import { abi, coder } from '../abi'; import { type TransactionClause } from '../transaction'; import type { DeployParams } from './types'; +import { VIP180_ABI } from '../utils'; +import { + DATA, + assert, + buildError +} from '@vechainfoundation/vechain-sdk-errors'; /** * Builds a clause for deploying a smart contract. @@ -58,10 +64,77 @@ function functionInteraction( return clause; } +/** + * Builds a clause for transferring VIP180 tokens. + * + * @param tokenAddress - The address of the VIP180 token. + * @param recipientAddress - The address of the recipient. + * @param amount - The amount of tokens to transfer in the decimals of the token. + * For instance, a token with 18 decimals, 1 token would be 1000000000000000000 (i.e., 10 ** 18). + * + * @returns A clause for transferring VIP180 tokens. + * + * @throws Will throw an error if the amount is not an integer or if the encoding of the function input fails. + */ +function transferToken( + tokenAddress: string, + recipientAddress: string, + amount: number | bigint | string +): TransactionClause { + try { + return functionInteraction(tokenAddress, VIP180_ABI, 'transfer', [ + recipientAddress, + BigInt(amount) + ]); + } catch (error) { + throw buildError( + DATA.INVALID_DATA_TYPE, + `Invalid 'amount' parameter. Expected an integer but received ${amount}` + ); + } +} + +/** + * Builds a clause for transferring VET. + * + * @param recipientAddress - The address of the recipient. + * @param amount - The amount of VET to transfer in wei. + * @returns A clause for transferring VET. + * + * @throws Will throw an error if the amount is not an integer. + */ +function transferVET( + recipientAddress: string, + amount: number | bigint | string +): TransactionClause { + try { + const bnAmount = BigInt(amount); + + // The amount must be positive otherwise we would be transferring negative VET. + assert( + bnAmount > 0, + DATA.INVALID_DATA_TYPE, + `Invalid 'amount' parameter. Expected a positive amount but received ${amount}` + ); + + return { + to: recipientAddress, + value: `0x${BigInt(amount).toString(16)}`, + data: '0x' + }; + } catch (error) { + throw buildError( + DATA.INVALID_DATA_TYPE, + `Invalid 'amount' parameter. Expected an integer but received ${amount}` + ); + } +} /** * clauseBuilder provides methods for building clauses for interacting with smart contracts or deploying smart contracts. */ export const clauseBuilder = { deployContract, - functionInteraction + functionInteraction, + transferToken, + transferVET }; diff --git a/packages/core/src/contract/coder.ts b/packages/core/src/contract/coder.ts index 56d2b6bba..4409799e6 100644 --- a/packages/core/src/contract/coder.ts +++ b/packages/core/src/contract/coder.ts @@ -33,7 +33,10 @@ function encodeFunctionInput( } catch (e) { throw buildError( ERROR_CODES.ABI.INVALID_DATA_TO_ENCODE, - 'Encoding failed: Function input must match ABI specifications and be correctly formatted', + `Method 'encodeFunctionInput' failed while encoding input for function '${functionName}'. ` + + `Input must match ABI specifications and be correctly formatted.\n` + + `Parameters: ${JSON.stringify(functionData)}\n` + + `Ethers' error message: ${(e as Error).message}`, { functionName, functionData }, e ); @@ -46,6 +49,7 @@ function encodeFunctionInput( * @param functionName The name of the function defined in the ABI. * @param encodedFunction The encoded function data. * @returns an array of the decoded function data + * * @throws {InvalidAbiDataToDecodeError} */ function decodeFunctionInput( diff --git a/packages/core/src/utils/const/abi.ts b/packages/core/src/utils/const/abi.ts index a24e3e53c..8a897e1ef 100644 --- a/packages/core/src/utils/const/abi.ts +++ b/packages/core/src/utils/const/abi.ts @@ -74,4 +74,268 @@ const PARAMS_ABI = JSON.stringify([ } ]); -export { PARAMS_ABI }; +/** + * ABI of the VIP180 token standard. + * + * @see [VIP 180](https://github.com/vechain/VIPs/blob/master/vips/VIP-180.md) + */ +const VIP180_ABI = JSON.stringify([ + { + constant: true, + inputs: [], + name: 'name', + outputs: [ + { + name: '', + type: 'string' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: false, + inputs: [ + { + name: '_spender', + type: 'address' + }, + { + name: '_value', + type: 'uint256' + } + ], + name: 'approve', + outputs: [ + { + name: 'success', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'totalSupply', + outputs: [ + { + name: '', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: false, + inputs: [ + { + name: '_from', + type: 'address' + }, + { + name: '_to', + type: 'address' + }, + { + name: '_amount', + type: 'uint256' + } + ], + name: 'transferFrom', + outputs: [ + { + name: 'success', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'decimals', + outputs: [ + { + name: '', + type: 'uint8' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address' + } + ], + name: 'balanceOf', + outputs: [ + { + name: 'balance', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'symbol', + outputs: [ + { + name: '', + type: 'string' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: false, + inputs: [ + { + name: '_to', + type: 'address' + }, + { + name: '_amount', + type: 'uint256' + } + ], + name: 'transfer', + outputs: [ + { + name: 'success', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + }, + { + constant: false, + inputs: [ + { + name: '_from', + type: 'address' + }, + { + name: '_to', + type: 'address' + }, + { + name: '_amount', + type: 'uint256' + } + ], + name: 'move', + outputs: [ + { + name: 'success', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'totalBurned', + outputs: [ + { + name: '', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address' + }, + { + name: '_spender', + type: 'address' + } + ], + name: 'allowance', + outputs: [ + { + name: 'remaining', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: '_from', + type: 'address' + }, + { + indexed: true, + name: '_to', + type: 'address' + }, + { + indexed: false, + name: '_value', + type: 'uint256' + } + ], + name: 'Transfer', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: '_owner', + type: 'address' + }, + { + indexed: true, + name: '_spender', + type: 'address' + }, + { + indexed: false, + name: '_value', + type: 'uint256' + } + ], + name: 'Approval', + type: 'event' + } +]); + +export { PARAMS_ABI, VIP180_ABI }; diff --git a/packages/core/tests/contract/contract.test.ts b/packages/core/tests/contract/contract.test.ts index e8d671959..f8159cb8f 100644 --- a/packages/core/tests/contract/contract.test.ts +++ b/packages/core/tests/contract/contract.test.ts @@ -4,7 +4,11 @@ import { contract, type DeployParams } from '../../src'; import { coder } from '../../src'; import { compileERC20SampleTokenContract, - getContractSourceCode + getContractSourceCode, + invalidTransferTokenClausesTestCases, + invalidTransferVETtestCases, + transferTokenClausesTestCases, + transferVETtestCases } from './fixture'; /** @@ -110,4 +114,75 @@ describe('Contract', () => { console.log(error); } }); + + describe('Transfer token clause builder test cases', () => { + /** + * Transfer token clause builder test cases. + */ + transferTokenClausesTestCases.forEach( + ({ tokenAddress, recipientAddress, amount, expected }) => { + test(`Build a clause to transfer ${amount} tokens`, () => { + const clause = contract.clauseBuilder.transferToken( + tokenAddress, + recipientAddress, + amount + ); + + expect(clause.to).toBe(tokenAddress); + expect(clause.value).toBe(0); + expect(clause).toStrictEqual(expected); + }); + } + ); + + /** + * Invalid transfer token clause builder test cases. + */ + invalidTransferTokenClausesTestCases.forEach( + ({ tokenAddress, recipientAddress, amount, expectedError }) => { + test(`Build a clause to transfer ${amount} tokens`, () => { + expect(() => { + contract.clauseBuilder.transferToken( + tokenAddress, + recipientAddress, + amount + ); + }).toThrowError(expectedError); + }); + } + ); + + /** + * Transfer VET clause builder test cases. + */ + transferVETtestCases.forEach( + ({ recipientAddress, amount, expected }) => { + test(`Build a clause to transfer ${amount} VET`, () => { + const clause = contract.clauseBuilder.transferVET( + recipientAddress, + amount + ); + + expect(clause.to).toBe(recipientAddress); + expect(clause).toStrictEqual(expected); + }); + } + ); + + /** + * Invalid transfer VET clause builder test cases. + */ + invalidTransferVETtestCases.forEach( + ({ recipientAddress, amount, expectedError }) => { + test(`Build a clause to transfer ${amount} VET`, () => { + expect(() => { + contract.clauseBuilder.transferVET( + recipientAddress, + amount + ); + }).toThrowError(expectedError); + }); + } + ); + }); }); diff --git a/packages/core/tests/contract/fixture.ts b/packages/core/tests/contract/fixture.ts index 3541c5990..7543811b6 100644 --- a/packages/core/tests/contract/fixture.ts +++ b/packages/core/tests/contract/fixture.ts @@ -2,6 +2,9 @@ import path from 'path'; import fs from 'fs'; import { compileContract, type Contract, type Sources } from './compiler'; +import { unitsUtils, VTHO_ADDRESS } from '../../src'; +import { generateRandomValidAddress } from '../fixture'; +import { InvalidDataTypeError } from '@vechainfoundation/vechain-sdk-errors'; const getContractSourceCode = (dirname: string, filename: string): string => { const contractPath = path.resolve(dirname, filename); @@ -62,4 +65,194 @@ function compileERC20SampleTokenContract(): Contract { return compileContract('SampleToken', erc20Sources); } -export { compileERC20SampleTokenContract, getContractSourceCode }; +/** + * Generates a random valid address. + */ +const recipientAddress = generateRandomValidAddress(); + +/** + * Test cases for building clauses for transferring VIP180 tokens. + */ +const transferTokenClausesTestCases = [ + { + tokenAddress: VTHO_ADDRESS, + recipientAddress, + amount: 1, + expected: { + to: VTHO_ADDRESS, + value: 0, + data: `0xa9059cbb000000000000000000000000${recipientAddress.slice( + 2 + )}0000000000000000000000000000000000000000000000000000000000000001` + } + }, + { + tokenAddress: VTHO_ADDRESS, + recipientAddress, + amount: '1', + expected: { + to: VTHO_ADDRESS, + value: 0, + data: `0xa9059cbb000000000000000000000000${recipientAddress.slice( + 2 + )}0000000000000000000000000000000000000000000000000000000000000001` + } + }, + { + tokenAddress: VTHO_ADDRESS, + recipientAddress, + amount: unitsUtils.parseVET('1'), + expected: { + to: VTHO_ADDRESS, + value: 0, + data: `0xa9059cbb000000000000000000000000${recipientAddress.slice( + 2 + )}0000000000000000000000000000000000000000000000000de0b6b3a7640000` + } + }, + { + tokenAddress: VTHO_ADDRESS, + recipientAddress, + amount: unitsUtils.parseVET('500000000'), + expected: { + to: VTHO_ADDRESS, + value: 0, + data: `0xa9059cbb000000000000000000000000${recipientAddress.slice( + 2 + )}0000000000000000000000000000000000000000019d971e4fe8401e74000000` + } + }, + { + tokenAddress: VTHO_ADDRESS, + recipientAddress, + amount: Number(unitsUtils.parseUnits('1', 2)), + expected: { + to: VTHO_ADDRESS, + value: 0, + data: `0xa9059cbb000000000000000000000000${recipientAddress.slice( + 2 + )}0000000000000000000000000000000000000000000000000000000000000064` + } + } +]; + +/** + * Test cases for building clauses for transferring VIP180 tokens. + */ +const invalidTransferTokenClausesTestCases = [ + { + tokenAddress: VTHO_ADDRESS, + recipientAddress, + amount: Number(unitsUtils.parseUnits('-1', 2)), + expectedError: InvalidDataTypeError + }, + { + tokenAddress: VTHO_ADDRESS, + recipientAddress, + amount: -1, + expectedError: InvalidDataTypeError + }, + { + tokenAddress: VTHO_ADDRESS, + recipientAddress, + amount: '1,2', + expectedError: InvalidDataTypeError + }, + { + tokenAddress: VTHO_ADDRESS, + recipientAddress, + amount: 1.7, + expectedError: InvalidDataTypeError + } +]; + +/** + * Test cases for building clauses for transferring VET. + */ +const transferVETtestCases = [ + { + recipientAddress, + amount: 1, + expected: { + to: recipientAddress, + value: '0x1', + data: '0x' + } + }, + { + recipientAddress, + amount: '1', + expected: { + to: recipientAddress, + value: '0x1', + data: '0x' + } + }, + { + recipientAddress, + amount: unitsUtils.parseVET('1'), + expected: { + to: recipientAddress, + value: '0xde0b6b3a7640000', + data: '0x' + } + }, + { + recipientAddress, + amount: unitsUtils.parseVET('500000000'), + expected: { + to: recipientAddress, + value: '0x19d971e4fe8401e74000000', + data: '0x' + } + }, + { + recipientAddress, + amount: Number(unitsUtils.parseUnits('1', 2)), + expected: { + to: recipientAddress, + value: '0x64', + data: '0x' + } + } +]; + +/** + * Invalid Test cases for building clauses for transferring VET. + */ +const invalidTransferVETtestCases = [ + { + recipientAddress, + amount: Number(unitsUtils.parseUnits('-1', 2)), + expectedError: InvalidDataTypeError + }, + { + recipientAddress, + amount: -1, + expectedError: InvalidDataTypeError + }, + { + recipientAddress, + amount: '1,2', + expectedError: InvalidDataTypeError + }, + { + recipientAddress, + amount: '1.2', + expectedError: InvalidDataTypeError + }, + { + recipientAddress, + amount: 1.7, + expectedError: InvalidDataTypeError + } +]; + +export { + compileERC20SampleTokenContract, + getContractSourceCode, + transferTokenClausesTestCases, + invalidTransferTokenClausesTestCases, + transferVETtestCases, + invalidTransferVETtestCases +}; diff --git a/sonar-project.properties b/sonar-project.properties index 646739fe1..00754d810 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -10,3 +10,4 @@ sonar.coverage.exclusions= \ packages/**/index.ts, \ packages/**/*config.js, \ packages/**/jest* +sonar.cpd.exclusions=packages/core/**/const/abi.ts