Skip to content

Commit 8af0826

Browse files
author
ci-bot
committed
mcp: deploy and interact
1 parent 784af84 commit 8af0826

File tree

13 files changed

+186
-139
lines changed

13 files changed

+186
-139
lines changed

apps/remix-ide/src/blockchain/blockchain.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const profile = {
2525
name: 'blockchain',
2626
displayName: 'Blockchain',
2727
description: 'Blockchain - Logic',
28-
methods: ['dumpState', 'getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'isSmartAccount', 'getAccounts', 'web3VM', 'web3', 'getProvider', 'getCurrentProvider', 'getCurrentNetworkStatus', 'getCurrentNetworkCurrency', 'getAllProviders', 'getPinnedProviders', 'changeExecutionContext', 'getProviderObject', 'runTx', 'getBalanceInEther', 'getCurrentProvider'],
28+
methods: ['dumpState', 'getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'isSmartAccount', 'getAccounts', 'web3VM', 'web3', 'getProvider', 'getCurrentProvider', 'getCurrentNetworkStatus', 'getCurrentNetworkCurrency', 'getAllProviders', 'getPinnedProviders', 'changeExecutionContext', 'getProviderObject', 'runTx', 'getBalanceInEther', 'getCurrentProvider', 'deployContractAndLibraries', 'runOrCallContractMethod'],
2929

3030
version: packageJson.version
3131
}
@@ -546,7 +546,7 @@ export class Blockchain extends Plugin {
546546
if (txResult.receipt.status === false || txResult.receipt.status === '0x0' || txResult.receipt.status === 0) {
547547
return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`)
548548
}
549-
finalCb(null, selectedContract, address)
549+
finalCb(null, selectedContract, address, txResult)
550550
})
551551
}
552552

@@ -668,7 +668,7 @@ export class Blockchain extends Plugin {
668668
return txlistener
669669
}
670670

671-
runOrCallContractMethod(contractName, contractAbi, funABI, contract, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb) {
671+
runOrCallContractMethod(contractName, contractAbi, funABI, contract, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb, finalCb) {
672672
// contractsDetails is used to resolve libraries
673673
txFormat.buildData(
674674
contractName,
@@ -701,6 +701,7 @@ export class Blockchain extends Plugin {
701701
if (lookupOnly) {
702702
outputCb(returnValue)
703703
}
704+
if (finalCb) finalCb(error, {txResult, address: _address, returnValue})
704705
})
705706
},
706707
(msg) => {

libs/remix-ai-core/src/remix-mcp-server/handlers/DeploymentHandler.ts

Lines changed: 109 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ import {
1414
AccountInfo,
1515
ContractInteractionResult
1616
} from '../types/mcpTools';
17+
import { bytesToHex } from '@ethereumjs/util'
1718
import { Plugin } from '@remixproject/engine';
19+
import { getContractData } from '@remix-project/core-plugin'
20+
import type { TxResult } from '@remix-project/remix-lib';
21+
import type { TransactionReceipt } from 'web3'
22+
import web3 from 'web3'
1823

1924
/**
2025
* Deploy Contract Tool Handler
@@ -54,6 +59,10 @@ export class DeployContractHandler extends BaseToolHandler {
5459
account: {
5560
type: 'string',
5661
description: 'Account to deploy from (address or index)'
62+
},
63+
file: {
64+
type: 'string',
65+
description: 'The file containing the contract to deploy'
5766
}
5867
},
5968
required: ['contractName']
@@ -86,56 +95,54 @@ export class DeployContractHandler extends BaseToolHandler {
8695
async execute(args: DeployContractArgs, plugin: Plugin): Promise<IMCPToolResult> {
8796
try {
8897
// Get compilation result to find contract
89-
// TODO: Get actual compilation result
90-
const contracts = {}; // await plugin.solidity.getCompilationResult();
91-
92-
if (!contracts || Object.keys(contracts).length === 0) {
93-
return this.createErrorResult('No compiled contracts found. Please compile first.');
98+
const compilerAbstract = await plugin.call('compilerArtefacts', 'getCompilerAbstract', args.file) as any;
99+
const data = getContractData(args.contractName, compilerAbstract)
100+
if (!data) {
101+
return this.createErrorResult(`Could not retrieve contract data for '${args.contractName}'`);
94102
}
95103

96-
// Find the contract to deploy
97-
const contractKey = Object.keys(contracts).find(key =>
98-
key.includes(args.contractName)
99-
);
100-
101-
if (!contractKey) {
102-
return this.createErrorResult(`Contract '${args.contractName}' not found in compilation result`);
103-
}
104-
105-
// Get current account
106-
const accounts = await this.getAccounts(plugin);
107-
const deployAccount = args.account || accounts[0];
108-
109-
if (!deployAccount) {
110-
return this.createErrorResult('No account available for deployment');
104+
let txReturn
105+
try {
106+
txReturn = await new Promise(async (resolve, reject) => {
107+
const callbacks = { continueCb: (error, continueTxExecution, cancelCb) => {
108+
continueTxExecution()
109+
}, promptCb: () => {}, statusCb: () => {}, finalCb: (error, contractObject, address: string, txResult: TxResult) => {
110+
if (error) return reject(error)
111+
resolve({contractObject, address, txResult})
112+
}}
113+
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
114+
continueTxExecution(null)
115+
}
116+
const compilerContracts = await plugin.call('compilerArtefacts', 'getLastCompilationResult')
117+
plugin.call('blockchain', 'deployContractAndLibraries',
118+
data,
119+
args.constructorArgs ? args : [],
120+
null,
121+
compilerContracts.getData().contracts,
122+
callbacks,
123+
confirmationCb
124+
)
125+
})
126+
} catch (e) {
127+
return this.createErrorResult(`Deployment error: ${e.message}`);
111128
}
112-
113-
// Prepare deployment transaction
114-
const deploymentData = {
115-
contractName: args.contractName,
116-
account: deployAccount,
117-
constructorArgs: args.constructorArgs || [],
118-
gasLimit: args.gasLimit,
119-
gasPrice: args.gasPrice,
120-
value: args.value || '0'
121-
};
122-
123-
// TODO: Execute actual deployment via Remix Run Tab API
124-
const mockResult: DeploymentResult = {
125-
success: false,
126-
contractAddress: undefined,
127-
transactionHash: '0x' + Math.random().toString(16).substr(2, 64),
128-
gasUsed: args.gasLimit || 1000000,
129+
130+
131+
console.log('txReturn', txReturn)
132+
const receipt = (txReturn.txResult.receipt as TransactionReceipt)
133+
const result: DeploymentResult = {
134+
transactionHash: web3.utils.bytesToHex(receipt.transactionHash),
135+
gasUsed: web3.utils.toNumber(receipt.gasUsed),
129136
effectiveGasPrice: args.gasPrice || '20000000000',
130-
blockNumber: Math.floor(Math.random() * 1000000),
131-
logs: []
137+
blockNumber: web3.utils.toNumber(receipt.blockNumber),
138+
logs: receipt.logs,
139+
contractAddress: receipt.contractAddress,
140+
success: receipt.status === BigInt(1) ? true : false
132141
};
133142

134-
// Mock implementation - in real implementation, use Remix deployment API
135-
mockResult.success = true;
136-
mockResult.contractAddress = '0x' + Math.random().toString(16).substr(2, 40);
143+
plugin.call('udapp', 'addInstance', result.contractAddress, data.abi, args.contractName, data)
137144

138-
return this.createSuccessResult(mockResult);
145+
return this.createSuccessResult(result);
139146

140147
} catch (error) {
141148
return this.createErrorResult(`Deployment failed: ${error.message}`);
@@ -161,6 +168,11 @@ export class CallContractHandler extends BaseToolHandler {
161168
inputSchema = {
162169
type: 'object',
163170
properties: {
171+
contractName: {
172+
type: 'string',
173+
description: 'Contract name',
174+
pattern: '^0x[a-fA-F0-9]{40}$'
175+
},
164176
address: {
165177
type: 'string',
166178
description: 'Contract address',
@@ -238,43 +250,65 @@ export class CallContractHandler extends BaseToolHandler {
238250

239251
async execute(args: CallContractArgs, plugin: Plugin): Promise<IMCPToolResult> {
240252
try {
241-
// Find the method in ABI
242-
const method = args.abi.find((item: any) =>
243-
item.name === args.methodName && item.type === 'function'
244-
);
245-
246-
if (!method) {
247-
return this.createErrorResult(`Method '${args.methodName}' not found in ABI`);
248-
}
249-
250-
// Get accounts
251-
const accounts = await this.getAccounts(plugin);
252-
const callAccount = args.account || accounts[0];
253-
254-
if (!callAccount) {
255-
return this.createErrorResult('No account available for contract call');
253+
const funcABI = args.abi.find((item: any) => item.name === args.methodName && item.type === 'function')
254+
const isView = funcABI.stateMutability === 'view' || funcABI.stateMutability === 'pure';
255+
let txReturn
256+
try {
257+
txReturn = await new Promise(async (resolve, reject) => {
258+
const params = funcABI.type !== 'fallback' ? args.args.join(',') : ''
259+
plugin.call('blockchain', 'runOrCallContractMethod',
260+
args.contractName,
261+
args.abi,
262+
funcABI,
263+
undefined,
264+
args.args ? args : [],
265+
args.address,
266+
params,
267+
isView,
268+
(msg) => {
269+
// logMsg
270+
},
271+
(msg) => {
272+
// logCallback
273+
},
274+
(returnValue) => {
275+
// outputCb
276+
},
277+
(network, tx, gasEstimation, continueTxExecution, cancelCb) => {
278+
// confirmationCb
279+
continueTxExecution(null)
280+
},
281+
(error, continueTxExecution, cancelCb) => {
282+
// continueCb
283+
continueTxExecution()
284+
},
285+
(okCb, cancelCb) => {
286+
// promptCb
287+
},
288+
(error, cancelCb) => {
289+
// promptCb
290+
},
291+
(error, {txResult, address, returnValue}) => {
292+
if (error) return reject(error)
293+
resolve({txResult, address, returnValue})
294+
},
295+
)
296+
})
297+
} catch (e) {
298+
return this.createErrorResult(`Deployment error: ${e.message}`);
256299
}
257300

258-
// Determine if this is a view function or transaction
259-
const isView = method.stateMutability === 'view' || method.stateMutability === 'pure';
260-
261301
// TODO: Execute contract call via Remix Run Tab API
262-
const mockResult: ContractInteractionResult = {
263-
success: true,
264-
result: isView ? 'mock_view_result' : undefined,
265-
transactionHash: isView ? undefined : '0x' + Math.random().toString(16).substr(2, 64),
266-
gasUsed: isView ? 0 : (args.gasLimit || 100000),
267-
logs: []
302+
const receipt = (txReturn.txResult.receipt as TransactionReceipt)
303+
const result: ContractInteractionResult = {
304+
result: txReturn.returnValue,
305+
transactionHash: isView ? undefined : web3.utils.bytesToHex(receipt.transactionHash),
306+
gasUsed: web3.utils.toNumber(receipt.gasUsed),
307+
logs: receipt.logs,
308+
success: receipt.status === BigInt(1) ? true : false
268309
};
269310

270-
if (isView) {
271-
mockResult.result = `View function result for ${args.methodName}`;
272-
} else {
273-
mockResult.transactionHash = '0x' + Math.random().toString(16).substr(2, 64);
274-
mockResult.gasUsed = args.gasLimit || 100000;
275-
}
276-
277-
return this.createSuccessResult(mockResult);
311+
return this.createSuccessResult(result);
278312

279313
} catch (error) {
280314
return this.createErrorResult(`Contract call failed: ${error.message}`);

libs/remix-ai-core/src/remix-mcp-server/types/mcpTools.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,16 @@ export interface CompilerConfigArgs {
104104

105105
export interface DeployContractArgs {
106106
contractName: string;
107-
constructorArgs?: any[];
107+
constructorArgs: any[];
108108
gasLimit?: number;
109109
gasPrice?: string;
110110
value?: string;
111111
account?: string;
112+
file: string;
112113
}
113114

114115
export interface CallContractArgs {
116+
contractName: string;
115117
address: string;
116118
abi: any[];
117119
methodName: string;
@@ -254,17 +256,17 @@ export interface DeploymentResult {
254256
success: boolean;
255257
contractAddress?: string;
256258
transactionHash: string;
257-
gasUsed: number;
259+
gasUsed: number | bigint;
258260
effectiveGasPrice: string;
259-
blockNumber: number;
261+
blockNumber: number | bigint;
260262
logs: any[];
261263
}
262264

263265
export interface ContractInteractionResult {
264266
success: boolean;
265267
result?: any;
266268
transactionHash?: string;
267-
gasUsed?: number;
269+
gasUsed?: number | bigint;
268270
logs?: any[];
269271
error?: string;
270272
}

libs/remix-core-plugin/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export { OffsetToLineColumnConverter } from './lib/offset-line-to-column-convert
22
export { CompilerMetadata } from './lib/compiler-metadata'
33
export { FetchAndCompile } from './lib/compiler-fetch-and-compile'
44
export { CompilerImports } from './lib/compiler-content-imports'
5-
export { CompilerArtefacts } from './lib/compiler-artefacts'
5+
export { CompilerArtefacts, getContractData } from './lib/compiler-artefacts'
66
export { GistHandler } from './lib/gist-handler'
77
export * from './types/contract'
88
export { LinkLibraries, DeployLibraries } from './lib/link-libraries'

libs/remix-core-plugin/src/lib/compiler-artefacts.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
'use strict'
22
import { Plugin } from '@remixproject/engine'
3-
import { util } from '@remix-project/remix-lib'
3+
import { util, execution } from '@remix-project/remix-lib'
44
import { CompilerAbstract } from '@remix-project/remix-solidity'
55
import { toChecksumAddress } from '@ethereumjs/util'
6+
import { ContractData } from '../types/contract'
67

78
const profile = {
89
name: 'compilerArtefacts',
@@ -227,3 +228,44 @@ export class CompilerArtefacts extends Plugin {
227228
return found
228229
}
229230
}
231+
232+
export const getContractData = (contractName: string, compiler: CompilerAbstract): ContractData => {
233+
if (!contractName) return null
234+
// const compiler = plugin.compilersArtefacts[compilerAttributeName]
235+
236+
if (!compiler) return null
237+
238+
const contract = compiler.getContract(contractName)
239+
240+
return {
241+
name: contractName,
242+
contract: contract,
243+
compiler: compiler,
244+
abi: contract.object.abi,
245+
bytecodeObject: contract.object.evm.bytecode.object,
246+
bytecodeLinkReferences: contract.object.evm.bytecode.linkReferences,
247+
object: contract.object,
248+
deployedBytecode: contract.object.evm.deployedBytecode,
249+
getConstructorInterface: () => {
250+
return execution.txHelper.getConstructorInterface(contract.object.abi)
251+
},
252+
getConstructorInputs: () => {
253+
const constructorInterface = execution.txHelper.getConstructorInterface(contract.object.abi)
254+
return execution.txHelper.inputParametersDeclarationToString(constructorInterface.inputs)
255+
},
256+
isOverSizeLimit: async (args: string) => {
257+
const encodedParams = await execution.txFormat.encodeParams(args, execution.txHelper.getConstructorInterface(contract.object.abi))
258+
const bytecode = contract.object.evm.bytecode.object + (encodedParams as any).dataHex
259+
// https://eips.ethereum.org/EIPS/eip-3860
260+
const initCodeOversize = bytecode && (bytecode.length / 2 > 2 * 24576)
261+
const deployedBytecode = contract.object.evm.deployedBytecode
262+
// https://eips.ethereum.org/EIPS/eip-170
263+
const deployedBytecodeOversize = deployedBytecode && (deployedBytecode.object.length / 2 > 24576)
264+
return {
265+
overSizeEip3860: initCodeOversize,
266+
overSizeEip170: deployedBytecodeOversize
267+
}
268+
},
269+
metadata: contract.object.metadata
270+
}
271+
}

libs/remix-lib/src/execution/txFormat.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,13 +205,15 @@ export function buildData (contractName, contract, contracts, isConstructor, fun
205205
let data: Buffer | string = ''
206206
let dataHex = ''
207207

208-
if (params.indexOf('raw:0x') === 0) {
208+
if (!Array.isArray(params) && params.indexOf('raw:0x') === 0) {
209209
// in that case we consider that the input is already encoded and *does not* contain the method signature
210210
dataHex = params.replace('raw:0x', '')
211211
data = Buffer.from(dataHex, 'hex')
212212
} else {
213213
try {
214-
if (params.length > 0) {
214+
if (Array.isArray(params)) {
215+
funArgs = params
216+
} else if (params.length > 0) {
215217
funArgs = parseFunctionParams(params)
216218
}
217219
} catch (e) {

0 commit comments

Comments
 (0)