package org.ethereum.gui;
import org.ethereum.core.Account;
import org.ethereum.core.Block;
import org.ethereum.core.Transaction;
import org.ethereum.core.Wallet;
import org.ethereum.db.ContractDetails;
import org.ethereum.util.ByteUtil;
import org.ethereum.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.BigIntegers;
import org.spongycastle.util.encoders.Hex;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.plaf.ComboBoxUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.*;
import java.math.BigInteger;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
/**
* www.ethereumJ.com
* @author: Roman Mandeleil
* Created on: 18/05/14 22:21
*/
class ContractCallDialog extends JDialog implements MessageAwareDialog {
private static final long serialVersionUID = -7561153561155037293L;
private Logger logger = LoggerFactory.getLogger("ui");
private ContractCallDialog dialog;
private JComboBox<AccountWrapper> creatorAddressCombo;
private final JTextField gasInput;
private final JTextField contractAddrInput;
private JScrollPane contractDataInput;
private JTextArea msgDataTA;
private JLabel statusMsg = null;
private JLabel playLabel = null;
private JLabel rejectLabel = null;
private JLabel approveLabel = null;
public ContractCallDialog(Frame parent) {
super(parent, "Call Contract: ", false);
dialog = this;
contractAddrInput = new JTextField(5);
GUIUtils.addStyle(contractAddrInput, "Contract Address: ");
contractAddrInput.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
populateContractDetails();
}
});
}
});
contractAddrInput.setBounds(70, 30, 350, 45);
this.getContentPane().add(contractAddrInput);
gasInput = new JTextField(5);
GUIUtils.addStyle(gasInput, "Gas: ");
msgDataTA = new JTextArea();
msgDataTA.setLineWrap(true);
contractDataInput = new JScrollPane(msgDataTA);
GUIUtils.addStyle(msgDataTA, null, false);
GUIUtils.addStyle(contractDataInput, "Input:");
msgDataTA.setText("");
msgDataTA.setCaretPosition(0);
this.getContentPane().setBackground(Color.WHITE);
this.getContentPane().setLayout(null);
contractDataInput.setBounds(70, 80, 350, 165);
this.getContentPane().add(contractDataInput);
gasInput.setBounds(330, 260, 90, 45);
this.getContentPane().add(gasInput);
URL rejectIconURL = ClassLoader.getSystemResource("buttons/reject.png");
ImageIcon rejectIcon = new ImageIcon(rejectIconURL);
rejectLabel = new JLabel(rejectIcon);
rejectLabel.setToolTipText("Cancel");
rejectLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
URL playIconURL = ClassLoader.getSystemResource("buttons/play.png");
ImageIcon playIcon = new ImageIcon(playIconURL);
playLabel = new JLabel(playIcon);
playLabel.setToolTipText("Play Drafted");
playLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
playLabel.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
ContractCallDialog.this.playContractCall();
}}
);
playLabel.setBounds(438, 100, 42, 42);
this.getContentPane().add(playLabel);
JLabel statusMessage = new JLabel("");
statusMessage.setBounds(50, 360, 400, 50);
statusMessage.setHorizontalAlignment(SwingConstants.CENTER);
this.statusMsg = statusMessage;
this.getContentPane().add(statusMessage);
rejectLabel.setBounds(260, 325, 45, 45);
this.getContentPane().add(rejectLabel);
rejectLabel.setVisible(true);
rejectLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
dialog.dispose();
}
});
URL approveIconURL = ClassLoader.getSystemResource("buttons/approve.png");
ImageIcon approveIcon = new ImageIcon(approveIconURL);
approveLabel = new JLabel(approveIcon);
approveLabel.setToolTipText("Submit the transaction");
approveLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
approveLabel.setBounds(200, 325, 45, 45);
this.getContentPane().add(approveLabel);
approveLabel.setVisible(true);
approveLabel.addMouseListener(
new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
submitContractCall();
}
}
);
gasInput.setText("1000");
JComboBox<AccountWrapper> creatorAddressCombo = new JComboBox<AccountWrapper>() {
private static final long serialVersionUID = -3748305421049121671L;
@Override
public ComboBoxUI getUI() {
return super.getUI();
}
};
creatorAddressCombo.setOpaque(true);
creatorAddressCombo.setEnabled(true);
creatorAddressCombo.setBackground(Color.WHITE);
creatorAddressCombo.setFocusable(false);
this.creatorAddressCombo = creatorAddressCombo;
final Border line = BorderFactory.createLineBorder(Color.LIGHT_GRAY);
JComponent editor = (JComponent)(creatorAddressCombo.getEditor().getEditorComponent());
editor.setForeground(Color.RED);
Wallet wallet = UIEthereumManager.ethereum.getWallet();
Collection<Account> accounts = wallet.getAccountCollection();
for (Account account : accounts) {
creatorAddressCombo.addItem(new AccountWrapper(account));
}
creatorAddressCombo.setRenderer(new DefaultListCellRenderer() {
private static final long serialVersionUID = 6100091092527477892L;
@Override
public void paint(Graphics g) {
setBackground(Color.WHITE);
setForeground(new Color(143, 170, 220));
setFont(new Font("Monospaced", 0, 13));
setBorder(BorderFactory.createEmptyBorder());
super.paint(g);
}
});
creatorAddressCombo.setPopupVisible(false);
Object child = creatorAddressCombo.getAccessibleContext().getAccessibleChild(0);
BasicComboPopup popup = (BasicComboPopup)child;
JList list = popup.getList();
list.setSelectionBackground(Color.cyan);
list.setBorder(null);
for (int i = 0; i < creatorAddressCombo.getComponentCount(); i++) {
if (creatorAddressCombo.getComponent(i) instanceof CellRendererPane) {
CellRendererPane crp = ((CellRendererPane) (creatorAddressCombo.getComponent(i)));
}
if (creatorAddressCombo.getComponent(i) instanceof AbstractButton) {
((AbstractButton) creatorAddressCombo.getComponent(i)).setBorder(line);
}
}
creatorAddressCombo.setBounds(70, 267, 230, 36);
this.getContentPane().add(creatorAddressCombo);
this.getContentPane().revalidate();
this.getContentPane().repaint();
this.setResizable(false);
this.setVisible(true);
}
private void populateContractDetails() {
byte[] addr = Utils.addressStringToBytes(contractAddrInput.getText());
if(addr == null) {
alertStatusMsg("Not a valid contract address");
return;
}
ContractDetails contractDetails = UIEthereumManager.ethereum
.getRepository().getContractDetails(addr);
if (contractDetails == null) {
alertStatusMsg("No contract for that address");
return;
}
final byte[] programCode = contractDetails.getCode();
if (programCode == null || programCode.length == 0) {
alertStatusMsg("Such account exist but no code in the db");
return;
}
final Map storageMap = contractDetails.getStorage();
contractDataInput.setBounds(70, 80, 350, 145);
contractDataInput.setViewportView(msgDataTA);
URL expandIconURL = ClassLoader.getSystemResource("buttons/add-23x23.png");
ImageIcon expandIcon = new ImageIcon(expandIconURL);
final JLabel expandLabel = new JLabel(expandIcon);
expandLabel.setToolTipText("Cancel");
expandLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
expandLabel.setBounds(235, 232, 23, 23);
this.getContentPane().add(expandLabel);
expandLabel.setVisible(true);
final Border border = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
final JPanel detailPanel = new JPanel();
detailPanel.setBorder(border);
detailPanel.setBounds(135, 242, 230, 2);
final JPanel spacer = new JPanel();
spacer.setForeground(Color.white);
spacer.setBackground(Color.white);
spacer.setBorder(null);
spacer.setBounds(225, 232, 40, 20);
this.getContentPane().add(spacer);
this.getContentPane().add(detailPanel);
expandLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
ContractCallDialog.this.setSize(500, 530);
ContractCallDialog.this.creatorAddressCombo.setBounds(70, 367, 230, 36);
ContractCallDialog.this.gasInput.setBounds(330, 360, 90, 45);
ContractCallDialog.this.rejectLabel.setBounds(260, 425, 45, 45);
ContractCallDialog.this.approveLabel.setBounds(200, 425, 45, 45);
ContractCallDialog.this.statusMsg.setBounds(50, 460, 400, 50);
spacer.setVisible(false);
expandLabel.setVisible(false);
detailPanel.setVisible(false);
JTextField contractCode = new JTextField(15);
contractCode.setText(Hex.toHexString( programCode ));
GUIUtils.addStyle(contractCode, "Code: ");
contractCode.setBounds(70, 230, 350, 45);
JTable storage = new JTable(2, 2);
storage.setTableHeader(null);
storage.setShowGrid(false);
storage.setIntercellSpacing(new Dimension(15, 0));
storage.setCellSelectionEnabled(false);
GUIUtils.addStyle(storage);
JTableStorageModel tableModel = new JTableStorageModel(storageMap);
storage.setModel(tableModel);
JScrollPane scrollPane = new JScrollPane(storage);
scrollPane.setBorder(null);
scrollPane.getViewport().setBackground(Color.WHITE);
scrollPane.setBounds(70, 290, 350, 50);
ContractCallDialog.this.getContentPane().add(contractCode);
ContractCallDialog.this.getContentPane().add(scrollPane);
}
});
this.repaint();
}
private void playContractCall() {
byte[] addr = Utils.addressStringToBytes(contractAddrInput.getText());
if(addr == null) {
alertStatusMsg("Not a valid contract address");
return;
}
ContractDetails contractDetails = UIEthereumManager.ethereum
.getRepository().getContractDetails(addr);
if (contractDetails == null) {
alertStatusMsg("No contract for that address");
return;
}
final byte[] programCode = contractDetails.getCode();
if (programCode == null || programCode.length == 0) {
alertStatusMsg("Such account exist but no code in the db");
return;
}
Transaction tx = createTransaction();
if (tx == null) return;
Block lastBlock = UIEthereumManager.ethereum.getBlockchain().getBestBlock();
ProgramPlayDialog.createAndShowGUI(programCode, tx, lastBlock);
}
protected JRootPane createRootPane() {
Container parent = this.getParent();
if (parent != null) {
Dimension parentSize = parent.getSize();
Point p = parent.getLocation();
setLocation(p.x + parentSize.width / 4, p.y + 10);
}
JRootPane rootPane = new JRootPane();
KeyStroke stroke = KeyStroke.getKeyStroke("ESCAPE");
Action actionListener = new AbstractAction() {
public void actionPerformed(ActionEvent actionEvent) {
dispose();
}
};
InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
inputMap.put(stroke, "ESCAPE");
rootPane.getActionMap().put("ESCAPE", actionListener);
this.setSize(500, 430);
return rootPane;
}
public void infoStatusMsg(String text) {
this.statusMsg.setForeground(Color.GREEN.darker().darker());
this.statusMsg.setText(text);
}
public void alertStatusMsg(String text) {
this.statusMsg.setForeground(Color.RED);
this.statusMsg.setText(text);
}
public void submitContractCall() {
if (!UIEthereumManager.ethereum.isConnected()) {
dialog.alertStatusMsg("Not connected to any peer");
return;
}
Transaction tx = createTransaction();
if (tx == null) return;
if (logger.isInfoEnabled()) {
logger.info("tx.hash: {}", (new BigInteger(tx.getHash()).toString(16)));
}
// SwingWorker
DialogWorker worker = new DialogWorker(tx, this);
worker.execute();
}
private Transaction createTransaction() {
byte[] data;
if (!msgDataTA.getText().trim().equals("")) {
Object[] lexaList = msgDataTA.getText().split(",");
data = ByteUtil.encodeDataList(lexaList);
} else {
data = new byte[] {};
}
byte[] contractAddress = Hex.decode( contractAddrInput.getText());
Account account = ((AccountWrapper)creatorAddressCombo.getSelectedItem()).getAccount();
byte[] senderPrivKey = account.getEcKey().getPrivKeyBytes();
BigInteger nonce = account.getNonce();
BigInteger gasPrice = new BigInteger("10000000000000");
BigInteger gasBI = new BigInteger(gasInput.getText());
byte[] gasValue = BigIntegers.asUnsignedByteArray(gasBI);
BigInteger endowment = new BigInteger("1000");
if (logger.isInfoEnabled()) {
logger.info("Contract call:");
logger.info("tx.nonce: {}", nonce == null ? "null" : nonce.toString());
logger.info("tx.gasPrice: {}", Hex.toHexString(BigIntegers.asUnsignedByteArray( gasPrice )));
logger.info("tx.gasValue: {}", Hex.toHexString(gasValue));
logger.info("tx.address: {}", Hex.toHexString(contractAddress));
logger.info("tx.endowment: {}", Hex.toHexString(BigIntegers.asUnsignedByteArray( endowment)));
logger.info("tx.data: {}", Hex.toHexString(data));
}
Transaction tx = UIEthereumManager.ethereum.createTransaction(
nonce,
gasPrice, gasBI,
contractAddress, endowment, data);
try {
tx.sign(senderPrivKey);
} catch (Exception e1) {
dialog.alertStatusMsg("Failed to sign the transaction");
return null;
}
return tx;
}
public class AccountWrapper {
private Account account;
public AccountWrapper(Account account) {
this.account = account;
}
public Account getAccount() {
return account;
}
@Override
public String toString() {
String addressShort =
Utils.getAddressShortString(account.getEcKey().getAddress());
String valueShort = "0";
String result = String.format(" By: [%s] %s", addressShort,
valueShort);
return result;
}
}
private class JTableStorageModel extends DefaultTableModel {
private JTableStorageModel(Map<String, String> data) {
if (data != null) {
this.setColumnCount(2);
this.setRowCount(data.size());
int i = 0;
for (String key : data.keySet()) {
this.setValueAt(key, i, 0);
this.setValueAt(data.get(key), i, 1);
++i;
}
}
}
}
public static void main(String args[]) {
ContractCallDialog ccd = new ContractCallDialog(null);
ccd.setVisible(true);
ccd.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
ccd.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
UIEthereumManager.ethereum.close();
}
});
}
}