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: Better complex struct inputs #702

Merged
merged 28 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
314665b
set up test contracts
technophile-04 Jan 30, 2024
8e993fd
handle passing of simple struct values
technophile-04 Jan 30, 2024
1d82d16
handle array of tupe
technophile-04 Jan 30, 2024
6007a20
add collapsible to structs
technophile-04 Jan 30, 2024
fab1a41
add margin bottom when collapse is open
technophile-04 Jan 30, 2024
a839eb1
add array index to TupleArray
technophile-04 Jan 30, 2024
e1bb946
remov extra div in TupleArray
technophile-04 Jan 30, 2024
f91f51d
add multidimensional tuple contract example
technophile-04 Feb 1, 2024
deab4af
some progress with nested structs
technophile-04 Feb 4, 2024
3fb9558
everything works, need to handle type errors
technophile-04 Feb 5, 2024
986b5b7
remove basic console.logs
technophile-04 Feb 5, 2024
c7acd87
clean up types and fix types issues
technophile-04 Feb 5, 2024
be19b97
clean up unnecessary commented out code
technophile-04 Feb 5, 2024
3168bd2
update with proper naming of input names and tuples
technophile-04 Feb 5, 2024
de4f3a4
Merge branch 'main' into feat/better-structs
technophile-04 Feb 5, 2024
77db5d7
update test contract and use hardhat
technophile-04 Feb 5, 2024
549d199
return only sData in contract
technophile-04 Feb 5, 2024
eff950f
reset deployedContracts
technophile-04 Feb 5, 2024
4902731
put back external contracts
technophile-04 Feb 5, 2024
df3fd3e
allow 0 length TupleArray to be passed
technophile-04 Feb 10, 2024
1394397
add better structs to ReadOnlyFunctionForm too
technophile-04 Feb 10, 2024
e878d41
use secondary color for indenting line
technophile-04 Feb 11, 2024
cc89cfe
show tuple[index] instead of just index for 1D tuple arrays
technophile-04 Feb 13, 2024
943cb39
Merge branch 'main' into feat/better-structs
technophile-04 Feb 14, 2024
afc364d
Merge branch 'main' into feat/better-structs
technophile-04 Feb 14, 2024
ad6814d
put back YourContract.sol and deploy script
technophile-04 Feb 14, 2024
b3baeb0
remove console.logs
technophile-04 Feb 14, 2024
f9c4a0f
lock daisyUI version to 4.5.0
technophile-04 Feb 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 37 additions & 74 deletions packages/hardhat/contracts/YourContract.sol
Original file line number Diff line number Diff line change
@@ -1,87 +1,50 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.5 <0.9.0;
pragma abicoder v2;

// Useful for debugging. Remove when deploying to a live network.
import "hardhat/console.sol";

// Use openzeppelin to inherit battle-tested implementations (ERC20, ERC721, etc)
// import "@openzeppelin/contracts/access/Ownable.sol";

/**
* A smart contract that allows changing a state variable of the contract and tracking the changes
* It also allows the owner to withdraw the Ether in the contract
* @author BuidlGuidl
*/
contract YourContract {
// State Variables
address public immutable owner;
string public greeting = "Building Unstoppable Apps!!!";
bool public premium = false;
uint256 public totalCounter = 0;
mapping(address => uint) public userGreetingCounter;

// Events: a way to emit log statements from smart contract that can be listened to by external parties
event GreetingChange(
address indexed greetingSetter,
string newGreeting,
bool premium,
uint256 value
);

// Constructor: Called once on contract deployment
// Check packages/hardhat/deploy/00_deploy_your_contract.ts
constructor(address _owner) {
owner = _owner;
struct NestedStruct {
uint a;
SimpleStruct[][][] b;
}

// Modifier: used to define a set of rules that must be met before or after a function is executed
// Check the withdraw() function
modifier isOwner() {
// msg.sender: predefined variable that represents address of the account that called the current function
require(msg.sender == owner, "Not the Owner");
_;
struct SimpleStruct {
uint x;
uint y;
}

/**
* Function that allows anyone to change the state variable "greeting" of the contract and increase the counters
*
* @param _newGreeting (string memory) - new greeting to save on the contract
*/
function setGreeting(string memory _newGreeting) public payable {
// Print data to the hardhat chain console. Remove when deploying to a live network.
console.log(
"Setting new greeting '%s' from %s",
_newGreeting,
msg.sender
);
// State variables
NestedStruct public sData;
SimpleStruct public tData;
uint public valueData;

// Change state variables
greeting = _newGreeting;
totalCounter += 1;
userGreetingCounter[msg.sender] += 1;
// Function to update the data
function updateData(NestedStruct calldata _nestedStruct) public {
// Update state variables
sData = _nestedStruct; // Assigns the entire struct. For dynamic arrays, you might need more complex logic.
}

// msg.value: built-in global variable that represents the amount of ether sent with the transaction
if (msg.value > 0) {
premium = true;
} else {
premium = false;
// Read function which accepts _nestedStruct
function totalPassedStruct(
NestedStruct calldata _nestedStruct
) public pure returns (uint totalA, uint totalX, uint totalY) {
totalA = _nestedStruct.a;
uint totalXSum = 0;
uint totalYSum = 0;

for (uint i = 0; i < _nestedStruct.b.length; i++) {
for (uint j = 0; j < _nestedStruct.b[i].length; j++) {
for (uint k = 0; k < _nestedStruct.b[i][j].length; k++) {
totalXSum += _nestedStruct.b[i][j][k].x;
totalYSum += _nestedStruct.b[i][j][k].y;
}
}
}

// emit: keyword used to trigger an event
emit GreetingChange(msg.sender, _newGreeting, msg.value > 0, 0);
return (totalA, totalXSum, totalYSum);
}

/**
* Function that allows the owner to withdraw all the Ether in the contract
* The function can only be called by the owner of the contract as defined by the isOwner modifier
*/
function withdraw() public isOwner {
(bool success, ) = owner.call{ value: address(this).balance }("");
require(success, "Failed to send Ether");
// Function to get the current datahe current data
function geAllSData() public view returns (NestedStruct memory) {
return (sData);
}

/**
* Function that allows the contract to receive ETH
*/
receive() external payable {}
}
8 changes: 3 additions & 5 deletions packages/hardhat/deploy/00_deploy_your_contract.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { Contract } from "ethers";
// import { Contract } from "ethers";

/**
* Deploys a contract named "YourContract" using the deployer account and
Expand All @@ -24,17 +24,15 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn

await deploy("YourContract", {
from: deployer,
// Contract constructor arguments
args: [deployer],
log: true,
// autoMine: can be passed to the deploy function to make the deployment process faster on local networks by
// automatically mining the contract deployment transaction. There is no effect on live networks.
autoMine: true,
});

// Get the deployed contract to interact with it after deploying.
const yourContract = await hre.ethers.getContract<Contract>("YourContract", deployer);
console.log("👋 Initial greeting:", await yourContract.greeting());
// const yourContract = await hre.ethers.getContract<Contract>("YourContract", deployer);
// console.log("👋 Initial greeting:", await yourContract.greeting());
};

export default deployYourContract;
Expand Down
61 changes: 49 additions & 12 deletions packages/nextjs/app/debug/_components/contract/ContractInput.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use client";

import { Dispatch, SetStateAction } from "react";
import { Tuple } from "./Tuple";
import { TupleArray } from "./TupleArray";
import { AbiParameter } from "abitype";
import {
AddressInput,
Expand All @@ -10,6 +12,7 @@ import {
IntegerInput,
IntegerVariant,
} from "~~/components/scaffold-eth";
import { AbiParameterTuple } from "~~/utils/scaffold-eth/contract";

type ContractInputProps = {
setForm: Dispatch<SetStateAction<Record<string, any>>>;
Expand All @@ -31,17 +34,51 @@ export const ContractInput = ({ setForm, form, stateObjectKey, paramType }: Cont
},
};

if (paramType.type === "address") {
return <AddressInput {...inputProps} />;
} else if (paramType.type === "bytes32") {
return <Bytes32Input {...inputProps} />;
} else if (paramType.type === "bytes") {
return <BytesInput {...inputProps} />;
} else if (paramType.type === "string") {
return <InputBase {...inputProps} />;
} else if (paramType.type.includes("int") && !paramType.type.includes("[")) {
return <IntegerInput {...inputProps} variant={paramType.type as IntegerVariant} />;
}
const renderInput = () => {
switch (paramType.type) {
case "address":
return <AddressInput {...inputProps} />;
case "bytes32":
return <Bytes32Input {...inputProps} />;
case "bytes":
return <BytesInput {...inputProps} />;
case "string":
return <InputBase {...inputProps} />;
case "tuple":
return (
<Tuple
setParentForm={setForm}
parentForm={form}
abiTupleParameter={paramType as AbiParameterTuple}
parentStateObjectKey={stateObjectKey}
/>
);
default:
// Handling 'int' types and 'tuple[]' types
if (paramType.type.includes("int") && !paramType.type.includes("[")) {
return <IntegerInput {...inputProps} variant={paramType.type as IntegerVariant} />;
} else if (paramType.type.startsWith("tuple[")) {
return (
<TupleArray
setParentForm={setForm}
parentForm={form}
abiTupleParameter={paramType as AbiParameterTuple}
parentStateObjectKey={stateObjectKey}
/>
);
} else {
return <InputBase {...inputProps} />;
}
}
};

return <InputBase {...inputProps} />;
return (
<div className="flex flex-col gap-1 w-full pl-2">
<div className="flex items-center">
{paramType.name && <span className="text-xs font-medium mr-2 leading-none">{paramType.name}</span>}
<span className="block text-xs font-extralight leading-none">{paramType.type}</span>
</div>
{renderInput()}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getFunctionInputKey,
getInitialFormState,
getParsedContractFunctionArgs,
transformAbiFunction,
} from "~~/app/debug/_components/contract";
import { getParsedError, notification } from "~~/utils/scaffold-eth";

Expand Down Expand Up @@ -42,7 +43,8 @@ export const ReadOnlyFunctionForm = ({
},
});

const inputElements = abiFunction.inputs.map((input, inputIndex) => {
const transformedFunction = transformAbiFunction(abiFunction);
const inputElements = transformedFunction.inputs.map((input, inputIndex) => {
const key = getFunctionInputKey(abiFunction.name, input, inputIndex);
return (
<ContractInput
Expand Down
44 changes: 44 additions & 0 deletions packages/nextjs/app/debug/_components/contract/Tuple.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { ContractInput } from "./ContractInput";
import { getFunctionInputKey, getInitalTupleFormState } from "./utilsContract";
import { replacer } from "~~/utils/scaffold-eth/common";
import { AbiParameterTuple } from "~~/utils/scaffold-eth/contract";

type TupleProps = {
abiTupleParameter: AbiParameterTuple;
setParentForm: Dispatch<SetStateAction<Record<string, any>>>;
parentStateObjectKey: string;
parentForm: Record<string, any> | undefined;
};

export const Tuple = ({ abiTupleParameter, setParentForm, parentStateObjectKey }: TupleProps) => {
const [form, setForm] = useState<Record<string, any>>(() => getInitalTupleFormState(abiTupleParameter));

useEffect(() => {
const values = Object.values(form);
const argsStruct: Record<string, any> = {};
abiTupleParameter.components.forEach((component, componentIndex) => {
argsStruct[component.name || `input_${componentIndex}_`] = values[componentIndex];
});

setParentForm(parentForm => ({ ...parentForm, [parentStateObjectKey]: JSON.stringify(argsStruct, replacer) }));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(form, replacer)]);

return (
<div>
<div className="collapse collapse-arrow">
<input type="checkbox" className="min-h-fit peer" />
<div className="collapse-title p-0 min-h-fit peer-checked:mb-2">
<p className="m-0 p-0 text-[1rem]">{abiTupleParameter.internalType}</p>
</div>
<div className="ml-3 flex-col space-y-4 border-secondary/80 border-l-2 pl-2 collapse-content">
{abiTupleParameter?.components?.map((param, index) => {
const key = getFunctionInputKey(abiTupleParameter.name || "tuple", param, index);
return <ContractInput setForm={setForm} form={form} key={key} stateObjectKey={key} paramType={param} />;
})}
</div>
</div>
</div>
);
};
Loading
Loading