Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: proxy CLI to interact with contracts #126

Merged
merged 12 commits into from
Oct 6, 2024
5 changes: 5 additions & 0 deletions .changeset/fluffy-taxis-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fuels/proxy-cli': minor
---

Create CLI to interact with proxy contrat
2 changes: 2 additions & 0 deletions .github/workflows/release-npm-latest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ jobs:
npm dist-tag add @fuels/react-xstore@${{ env.BUILD_VERSION }} latest
npm dist-tag add @fuels/ts-config@${{ env.BUILD_VERSION }} latest
npm dist-tag add @fuels/tsup-config@${{ env.BUILD_VERSION }} latest
npm dist-tag add @fuels/proxy-cli@${{ env.BUILD_VERSION }} latest

30 changes: 30 additions & 0 deletions packages/proxy-actions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@fuels/proxy-cli",
"version": "0.24.0",
"description": "A cli tool to execute action on a proxy contract",
"license": "APACHE-2.0",
"bin": {
"fuels-proxy": "./dist/index.js"
},
"files": [
"./dist"
],
"scripts": {
"build": "tsup",
"dev": "ts-node ./src/index.ts",
"scripts": "node ./dist/index.js"
},
"peerDependencies": {
"fuels": "0.94.4"
},
"devDependencies": {
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
},
"dependencies": {
"@fuel-bridge/fungible-token": "^0.6.0",
"@fuels/kms-account": "0.24.1",
"@types/node": "^18.11.9",
"commander": "^12.1.0"
}
}
14 changes: 14 additions & 0 deletions packages/proxy-actions/src/actions/getBalance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Account } from 'fuels';

type GetBalanceParams = {
account: Account;
};

export async function getBalance({ account }: GetBalanceParams) {
const balance = await account.getBalance();
console.log(
`${account.address.toB256()}: ${balance.format({
precision: 9,
})}`,
);
}
27 changes: 27 additions & 0 deletions packages/proxy-actions/src/actions/getImplementation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Proxy } from '@fuel-bridge/fungible-token';
import { type Account } from 'fuels';

type GetImplementationParams = {
account: Account;
proxyAddress: string;
};

export async function getImplementation({
account,
proxyAddress,
}: GetImplementationParams) {
const proxy = new Proxy(proxyAddress, account);

console.log(`Proxy(${proxyAddress}) get implementation script initiated`);
console.log('\t> Account address: ', account.address.toB256());
console.log(
'\t> Balance: ',
(await account.getBalance()).format({
precision: 9,
}),
);

const { value: currentTarget } = await proxy.functions.proxy_target().get();

console.log(`\t> Implementation: ${currentTarget.bits}`);
}
44 changes: 44 additions & 0 deletions packages/proxy-actions/src/actions/getOwnership.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* This is a stand-alone script that upgrades the bridge
*/
import { Proxy } from '@fuel-bridge/fungible-token';
import type { Account } from 'fuels';

import { debug } from '../utils';

type GetOwnershipParams = {
account: Account;
proxyAddress: string;
};

export const getOwnership = async ({
account,
proxyAddress,
}: GetOwnershipParams) => {
const proxy = new Proxy(proxyAddress, account);

console.log(`Proxy(${proxyAddress}) get ownership script initiated`);
console.log(
'\t> Balance: ',
(await account.getBalance()).format({
precision: 9,
}),
);

debug('Detecting if the bridge is a proxy...');
const owner: string | null = await proxy.functions
._proxy_owner()
.get()
.then((result) => {
debug('Proxy._proxy.owner() succeeded, assuming proxy');
return result.value.Initialized.Address.bits;
})
.catch((e) => {
console.error(e);
debug(`Proxy._proxy_owner() failed with error: `);
debug(`${JSON.stringify(e, undefined, 2)}`);
return null;
});

console.log('\t> Owner: ', owner);
};
51 changes: 51 additions & 0 deletions packages/proxy-actions/src/actions/setImplementation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Proxy } from '@fuel-bridge/fungible-token';
import { Address, type Account } from 'fuels';

type SetImplementationParams = {
account: Account;
proxyAddress: string;
implementationAddress: string;
};

export async function setImplementation({
account,
proxyAddress,
implementationAddress,
}: SetImplementationParams) {
const proxy = new Proxy(proxyAddress, account);

console.log(`Proxy(${proxyAddress}) set implementation script initiated`);
console.log('\t> Owner address: ', account.address.toB256());
console.log(
'\t> Balance: ',
(await account.getBalance()).format({
precision: 9,
}),
);

const { id: contract_id } = await account.provider.getContract(
implementationAddress,
);
if (!contract_id) {
console.log(`\t> Implementation ${implementationAddress} not found`);
return;
}

const { value: currentTarget } = await proxy.functions.proxy_target().get();
if (currentTarget.bits === implementationAddress) {
console.log(
`\t> Implementation ${implementationAddress} is already live in the proxy`,
);
return;
}

console.log('\t> New implementation at ', implementationAddress);
const contractId = Address.fromB256(implementationAddress);
const contractIdentityInput = { bits: contractId.toB256() };
const tx = await proxy.functions
.set_proxy_target(contractIdentityInput)
.call();

console.log('\t> Transaction ID: ', tx.transactionId);
await tx.waitForResult();
}
69 changes: 69 additions & 0 deletions packages/proxy-actions/src/actions/transferOwnership.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* This is a stand-alone script that upgrades the bridge
*/
import { Proxy } from '@fuel-bridge/fungible-token';
import type { Account } from 'fuels';
import { Address } from 'fuels';

import { debug } from '../utils';

type TransferOwnershipParams = {
account: Account;
newOwner: string;
proxyAddress: string;
};

export const transferOwnership = async ({
account,
newOwner,
proxyAddress,
}: TransferOwnershipParams) => {
const proxy = new Proxy(proxyAddress, account);

console.log(`Proxy(${proxyAddress}) transfer ownership script initiated`);
console.log('\t> Owner address: ', account.address.toB256());
console.log(
'\t> Balance: ',
(await account.getBalance()).format({
precision: 9,
}),
);

debug('Detecting if contract is a proxy...');
const owner: string | null = await proxy.functions
._proxy_owner()
.get()
.then((result) => {
debug('Proxy._proxy.owner() succeeded, assuming proxy');
return result.value.Initialized.Address.bits;
})
.catch((e) => {
debug(`Proxy._proxy_owner() failed with error: `);
debug(`${JSON.stringify(e, undefined, 2)}`);
return null;
});

if (owner === null) {
console.log('Could not fetch the proxy owner, is it a proxy?');
return;
}

if (
owner.replace('0x', '').toLowerCase() !==
account.address.toB256().replace('0x', '').toLowerCase()
) {
console.log(`\t> Owner mismatch, contract owned by ${owner}`);
return;
}

// New owner should be a valid b256 address
const address = Address.fromB256(newOwner);
const addressInput = { bits: address.toB256() };
const addressIdentityInput = { Address: addressInput };
const tx = await proxy.functions
._proxy_change_owner(addressIdentityInput)
.call();

console.log('\t> Transaction ID: ', tx.transactionId);
await tx.waitForResult();
};
15 changes: 15 additions & 0 deletions packages/proxy-actions/src/actions/transferSelf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { bn, type Account } from 'fuels';

type GetBalanceParams = {
account: Account;
};

export async function transferSelf({ account }: GetBalanceParams) {
console.log(`\t> Account initiate a transfer of 0.0000001 to itself`);
const transfer = await account.transfer(
account.address,
bn.parseUnits('0.0000001'),
);
await transfer.waitForResult();
console.log(`\t> Transfer transaction ID: ${transfer.id}`);
}
117 changes: 117 additions & 0 deletions packages/proxy-actions/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Option, program } from 'commander';

import { getBalance } from './actions/getBalance';
import { getImplementation } from './actions/getImplementation';
import { getOwnership } from './actions/getOwnership';
import { setImplementation } from './actions/setImplementation';
import { transferOwnership } from './actions/transferOwnership';
import { transferSelf } from './actions/transferSelf';
import { createAccount } from './utils';

const optionProvider = new Option(
'--providerUrl <provider>',
'Provider URL is required!',
)
.env('PROVIDER_URL')
.makeOptionMandatory();
const optionAccountKey = new Option(
'-a, --account <account>',
'Account address is required!',
)
.env('ACCOUNT_KEY')
.makeOptionMandatory();

program
.name('Proxy scripts')
.description('Scripts for interacting with the proxy contract');

program
.command('balance')
.addOption(optionProvider)
.addOption(optionAccountKey)
.action(async (options) => {
const account = await createAccount(options.account, options.providerUrl);
await getBalance({ account });
});

program
.command('setImplementation')
.addOption(optionProvider)
.addOption(optionAccountKey)
.requiredOption(
'-p, --proxyAddress <proxyAddress>',
'Proxy address is required!',
)
.requiredOption(
'--implementationAddress <targetAddress>',
'Implementation address is required!',
)
.action(async (options) => {
const account = await createAccount(options.account, options.providerUrl);
await setImplementation({
account,
proxyAddress: options.proxyAddress,
implementationAddress: options.implementationAddress,
});
});

program
.command('getImplementation')
.addOption(optionProvider)
.addOption(optionAccountKey)
.requiredOption(
'-p, --proxyAddress <proxyAddress>',
'Proxy address is required!',
)
.action(async (options) => {
const account = await createAccount(options.account, options.providerUrl);
await getImplementation({
account,
proxyAddress: options.proxyAddress,
});
});

program
.command('getOwnership')
.addOption(optionProvider)
.addOption(optionAccountKey)
.requiredOption(
'-p, --proxyAddress <proxyAddress>',
'Proxy address is required!',
)
.action(async (options) => {
const account = await createAccount(options.account, options.providerUrl);
await getOwnership({
account,
proxyAddress: options.proxyAddress,
});
});

program
.command('transferOwnership')
.addOption(optionProvider)
.addOption(optionAccountKey)
.requiredOption(
'-p, --proxyAddress <proxyAddress>',
'Proxy address is required!',
)
.requiredOption('--newOwner <newOwner>', 'New owner address is required!')
.action(async (options) => {
const account = await createAccount(options.account, options.providerUrl);
await transferOwnership({
account,
proxyAddress: options.proxyAddress,
newOwner: options.newOwner,
});
});

program
.command('transferSelf')
.addOption(optionProvider)
.addOption(optionAccountKey)
.action(async (options) => {
const account = await createAccount(options.account, options.providerUrl);
await transferSelf({ account });
});

program.parse().showSuggestionAfterError();
Loading
Loading