Skip to content

Commit

Permalink
Merge pull request #4 from LayerZero-Labs/tron-helpers
Browse files Browse the repository at this point in the history
feat: execute helper support for Tron
  • Loading branch information
aurel-fr authored Jan 15, 2024
2 parents 027fe3c + b254ea4 commit cf64c6b
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 44 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
name: Publish Package to npmjs
on:
push:
branches: [ci-testing]
release:
types: [published]
jobs:
Expand All @@ -16,6 +18,8 @@ jobs:
with:
version: 8
- run: pnpm install
- run: pnpm test
- run: npm publish --access=restricted
if: ${{ github.event_name == 'release' && github.event.action == 'published' }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
7 changes: 7 additions & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"require": "ts-node/register/files",
"exit": true,
"full-trace": false,
"timeout": 30000,
"parallel": false
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Hardhat Plugin For Replicable Deployments And Tests",
"repository": {
"type": "git",
"url": "git+https://github.com/wighawag/hardhat-deploy.git"
"url": "git+https://github.com/LayerZero-Labs/hardhat-deploy.git"
},
"author": "wighawag",
"license": "MIT",
Expand Down Expand Up @@ -95,7 +95,7 @@
"lint": "eslint \"**/*.{js,ts}\" && solhint src/**/*.sol",
"lint:fix": "eslint --fix \"**/*.{js,ts}\" && solhint --fix src/**/*.sol",
"format": "prettier --write \"**/*.{ts,js,sol}\"",
"test": "mocha --timeout 20000 --exit",
"test": "mocha './test/*.test.ts'",
"build": "rm -rf ./dist && tsc",
"watch": "tsc -w",
"publish:next": "npm publish --tag next",
Expand Down
11 changes: 8 additions & 3 deletions src/DeploymentsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export class DeploymentsManager {
public impersonateUnknownAccounts: boolean;
public impersonatedAccounts: string[];
public addressesToProtocol: {[address: string]: string} = {};
public readonly isTronNetworkWithTronSolc: boolean;

private network: Network;

Expand Down Expand Up @@ -136,6 +137,8 @@ export class DeploymentsManager {
runAsNode: false,
};
this.env = env;
this.isTronNetworkWithTronSolc =
network.tron && (this.env.config as any)?.tronSolc?.enable;
this.deploymentsPath = env.config.paths.deployments;

// TODO
Expand Down Expand Up @@ -213,6 +216,8 @@ export class DeploymentsManager {
contractName: string
): Promise<ExtendedArtifact> => {
if (this.db.onlyArtifacts) {
// For the Tron network there is already a mechanism in this file to ensure onlyArtifacts can only point to artifacts folder ending in -tron
// We assume those artifacts folder ending in -tron are compatible with Tron and have been compiled with tron-solc
const artifactFromFolder = await getExtendedArtifactFromFolders(
contractName,
this.db.onlyArtifacts
Expand Down Expand Up @@ -1052,7 +1057,7 @@ export class DeploymentsManager {
if (externalContracts.deploy) {
// make sure we're not deploying on Tron contracts that are not meant to be deployed there
if (
this.env.config.paths.artifacts.endsWith('-tron') && // are we using tron-solc compiler?
this.isTronNetworkWithTronSolc && // are we using tron-solc compiler and is the network a Tron network?
externalContracts.artifacts.some((str) => !str.endsWith('-tron')) // are some of the artifacts folder not ending in -tron?
) {
continue;
Expand Down Expand Up @@ -1419,8 +1424,8 @@ export class DeploymentsManager {
}

private getImportPaths() {
// this check is true when the hardhat-tron-solc plugin is used
if (this.env.config.paths.artifacts.endsWith('-tron')) {
// this check is true when the hardhat-tron-solc plugin is used and the network is Tron
if (this.isTronNetworkWithTronSolc) {
return this.getTronImportPaths();
}
const importPaths = [this.env.config.paths.imports];
Expand Down
38 changes: 28 additions & 10 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ export function addHelpers(
if (options.deterministicDeployment) {
// feature not ready for Tron yet
if (network.tron) {
throw new Error('deterministic deployment not yet supported on Tron');
throw new Error('deterministic deployment not supported on Tron');
}
if (typeof unsignedTx.data === 'string') {
const create2DeployerAddress = await ensureCreate2DeployerReady(
Expand Down Expand Up @@ -2712,10 +2712,28 @@ data: ${data}
}
}

tx = await handleSpecificErrors(
ethersContract.functions[methodName](...ethersArgs)
);

if (network.tron) {
const method = ethersContract.interface.getFunction(methodName);
const methodParams = method.inputs.map((input) => input.type);
const funcSig = `${methodName}(${methodParams.join(',')})`;
const tronArgs = args.map((a, i) => ({
type: methodParams[i],
value: a,
}));
tx = await handleSpecificErrors(
(ethersSigner.provider as TronWeb3Provider).triggerSmartContract(
from,
deployment.address,
funcSig,
tronArgs,
overrides
)
);
} else {
tx = await handleSpecificErrors(
ethersContract.functions[methodName](...ethersArgs)
);
}
tx = await onPendingTx(tx);

if (options.log || hardwareWallet) {
Expand Down Expand Up @@ -2832,16 +2850,16 @@ data: ${data}
const extension: DeploymentsExtension = {
...partialExtension,
fetchIfDifferent,
deploy,
deploy, // tron compatible
diamond: {
deploy: diamond,
},
catchUnknownSigner,
execute,
execute, // tron compatible
rawTx,
read,
deterministic,
getSigner,
read, // tron compatible
deterministic, // won't support tron (contracts addresses are dependent on timestamps)
getSigner, // tron compatible
};

const utils = {
Expand Down
143 changes: 130 additions & 13 deletions src/tron/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ import {
TronTransactionFailedError,
TronWebError,
ensure0x,
strip0x,
} from './utils';
import {Deferrable, HDNode, parseTransaction} from 'ethers/lib/utils';
import {
Deferrable,
HDNode,
isAddress,
parseTransaction,
} from 'ethers/lib/utils';
import TronWeb from 'tronweb';
import {TronWebError1} from 'tronweb/interfaces';
import {Transaction, TronWebError1} from 'tronweb/interfaces';

/**
* A provider for interacting with the TRON blockchain, extending the Web3Provider.
Expand All @@ -44,7 +50,7 @@ import {TronWebError1} from 'tronweb/interfaces';
* @param {Networkish | undefined} [network] - The network configuration.
*/
export class TronWeb3Provider extends Web3Provider {
protected signer = new Map<string, TronSigner>();
protected signers = new Map<string, TronSigner>();
public ro_tronweb: TronWeb;
public gasPrice: {time: number; value?: BigNumber} = {time: Time.NOW};
private readonly fullHost: string;
Expand All @@ -67,7 +73,7 @@ export class TronWeb3Provider extends Web3Provider {
if (Array.isArray(accounts)) {
for (const pk of accounts) {
const addr = new Wallet(pk).address;
this.signer.set(addr, new TronSigner(fullHost, headers, pk, this));
this.signers.set(addr, new TronSigner(fullHost, headers, pk, this));
}
} else if (typeof accounts !== 'string' && 'mnemonic' in accounts) {
const hdNode = HDNode.fromMnemonic(
Expand All @@ -77,7 +83,7 @@ export class TronWeb3Provider extends Web3Provider {
const derivedNode = hdNode.derivePath(
`${accounts.path}/${accounts.initialIndex}`
);
this.signer.set(
this.signers.set(
derivedNode.address,
new TronSigner(fullHost, headers, derivedNode.privateKey, this)
);
Expand All @@ -100,9 +106,9 @@ export class TronWeb3Provider extends Web3Provider {
*/
addSigner(pk: string): TronSigner {
const addr = new Wallet(pk).address;
if (this.signer.has(addr)) return this.signer.get(addr)!;
if (this.signers.has(addr)) return this.signers.get(addr)!;
const signer = new TronSigner(this.fullHost, this.headers, pk, this);
this.signer.set(addr, signer);
this.signers.set(addr, signer);
return signer;
}

Expand Down Expand Up @@ -135,7 +141,7 @@ export class TronWeb3Provider extends Web3Provider {
override getSigner<T extends TronSigner | JsonRpcSigner = JsonRpcSigner>(
address: string
): T {
const signer = this.signer.get(address);
const signer = this.signers.get(address);
if (!signer) {
throw new Error(`No Tron signer exists for this address ${address}`);
}
Expand Down Expand Up @@ -175,11 +181,17 @@ export class TronWeb3Provider extends Web3Provider {
signedTransaction = await signedTransaction;
const deser = parseTransaction(signedTransaction);
const {to, data, from, value} = deser;
// is this a send eth transaction?
if (to && from && (!data || data == '0x')) {
return this.sendTrx(from, to, value);

// is this a send trx transaction?
if (this.isSendTRX(to, from, data)) {
return this.sendTrx(from!, to!, value);
}
// is this a smart contract transaction?
if (await this.isSmartContractCall(to, from, data)) {
throw new Error(
'direct smart contract call not yet implemented for Tron'
);
}
// TODO, smart contract calls, etc

// otherwise don't alter behavior
return super.sendTransaction(signedTransaction);
Expand Down Expand Up @@ -211,10 +223,72 @@ export class TronWeb3Provider extends Web3Provider {
this.ro_tronweb.address.toHex(from)
);
const signedTx = await this.getSigner<TronSigner>(from).sign(unsignedTx);
const response = await this.ro_tronweb.trx.sendRawTransaction(signedTx);
return this.sendRawTransaction(signedTx);
}

/**
* Triggers a function call on a specified smart contract in the Tron network.
*
* This method constructs a transaction to call a function of a smart contract. It requires
* the sender's address, the contract address, the function signature, parameters for the function,
* and an options object which may include a gas limit and an optional value to send with the transaction.
* The fee limit for the transaction is determined using the sender's signer. The transaction
* is then signed and sent to the Tron network.
*
* @param from - The address of the sender initiating the contract call.
* @param contract - The address of the smart contract to interact with.
* @param funcSig - The function signature to call in the smart contract.
* @param params - An array of parameters for the function call, each with a type and value.
* @param options - An object containing optional parameters.
* @returns A promise that resolves to a `TransactionResponse` object representing the result of the transaction.
*/
async triggerSmartContract(
from: string,
contract: string,
funcSig: string,
params: {type: string; value: string | number}[],
options: {
gasLimit?: string | number | BigNumber;
value?: string | BigNumber;
}
) {
const feeLimit = await this.getSigner<TronSigner>(from).getFeeLimit(
{to: contract},
options
);
const {transaction} =
await this.ro_tronweb.transactionBuilder.triggerSmartContract(
this.ro_tronweb.address.toHex(contract),
funcSig,
{feeLimit, callValue: options.value?.toString() ?? 0},
params,
this.ro_tronweb.address.toHex(from)
);
const signedTx = await this.getSigner<TronSigner>(from).sign(transaction);
return this.sendRawTransaction(signedTx);
}

/**
* Sends a raw transaction to the Tron network and returns the transaction response.
*
* This method accepts a raw transaction object, sends it to the Tron network, and waits
* for the transaction to be acknowledged. After the transaction is acknowledged, it retrieves
* and returns the transaction response. If the transaction fails at any stage, the method
* throws an error.
*
* @param transaction - The raw transaction object to be sent to the network.
* @returns A promise that resolves to a `TransactionResponse` object, which includes details of the processed transaction.
* @throws `TronWebError` - If the transaction fails to be sent or acknowledged by the network.
*
*/
async sendRawTransaction(
transaction: Transaction
): Promise<TransactionResponse> {
const response = await this.ro_tronweb.trx.sendRawTransaction(transaction);
if (!('result' in response) || !response.result) {
throw new TronWebError(response as TronWebError1);
}
console.log('\nTron transaction broadcast, waiting for response...');
const txRes = await this.getTransactionWithRetry(response.txid);
txRes.wait = this._buildWait(txRes.confirmations, response.txid);
return txRes;
Expand Down Expand Up @@ -302,4 +376,47 @@ export class TronWeb3Provider extends Web3Provider {
}
return super.estimateGas(transaction);
}

/**
* Checks if a given transaction is a smart contract call.
*
* This method examines the `to`, `from`, and `data` fields of a transaction
* to determine if it is likely a call to a smart contract. It considers a transaction
* as a smart contract call if all fields are defined, the addresses are valid,
* the data field has a significant length, and there is associated contract code.
*
* @param to - The recipient address of the transaction.
* @param from - The sender address of the transaction.
* @param data - The data payload of the transaction.
* @returns A promise that resolves to `true` if the transaction is a smart contract call, otherwise `false`.
*/
async isSmartContractCall(
to?: string,
from?: string,
data?: string
): Promise<boolean> {
if ([to, from, data].some((f) => f == undefined)) return false;
if ([to, from].some((f) => isAddress(f!) == false)) return false;
if (data!.length <= 2) return false;
const contractCode = await this.getCode(to!);
return contractCode != undefined && strip0x(contractCode).length > 0;
}

/**
* Determines if a transaction is a TRX (transfer) operation.
*
* This method checks if the provided `to`, `from`, and `data` fields
* of a transaction suggest a TRX operation. It considers a transaction as
* a TRX operation if the `to` and `from` fields are defined and the `data` field
* is either not present or equals '0x'.
*
* @param to - The recipient address of the transaction.
* @param from - The sender address of the transaction.
* @param data - The data payload of the transaction.
* @returns `true` if the transaction is likely a TRX operation, otherwise `false`.
*/
isSendTRX(to?: string, from?: string, data?: string): boolean {
if ([to, from].some((f) => f == undefined)) return false;
return !data || data == '0x';
}
}
13 changes: 2 additions & 11 deletions src/tron/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {BigNumber, Wallet} from 'ethers';
import {Deferrable} from 'ethers/lib/utils';
import TronWeb from 'tronweb';
import {TronWeb3Provider} from './provider';
import {Time, TronWebGetTransactionError, ensure0x, strip0x} from './utils';
import {Time, TronWebGetTransactionError, strip0x} from './utils';
import {CreateSmartContract, MethodSymbol, TronTxMethods} from './types';
import {TronWebError} from './utils';
import {
Expand Down Expand Up @@ -135,16 +135,7 @@ export class TronSigner extends Wallet {
);

const signedTx = await this.sign(unsignedTx);

const response = await this.tronweb.trx.sendRawTransaction(signedTx);
if (!('result' in response) || !response.result) {
throw new TronWebError(response as TronWebError1); // in this case tronweb returs an error-like object with a message and a code
}
console.log('\nTransaction broadcast, waiting for response...');
const provider = this.provider as TronWeb3Provider;
const txRes = await provider.getTransactionWithRetry(response.txid);
txRes.wait = provider._buildWait(txRes.confirmations, response.txid);
return txRes;
return (this.provider as TronWeb3Provider).sendRawTransaction(signedTx);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/tron/tronweb.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ declare module 'tronweb' {
options: Record<string, unknown>,
parameter: any[],
issuerAddress: string
): Promise<TriggerConstantContractResult | Record<string, unknown>>;
): Promise<TriggerConstantContractResult>;
undelegateResource(
amount: number,
receiverAddress: string,
Expand Down
Loading

0 comments on commit cf64c6b

Please sign in to comment.