How to define TYPEHASH for EIP712 typed data signing with nested struct in Solidity? - ethereum

I am wondering what the correct way is to define the TYPEHASH for a nested struct data structure for the EIP-712. I am trying to do this, as I want to retrieve the signer of a request struct using ECDSA and the EIP-712 standard for hashing structs.
This is the contract:
import "#openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "#openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract SignatureChecker is EIP712 {
using ECDSA for bytes32;
struct Fee {
address recipient;
uint256 value;
}
struct Request {
address to;
address from;
Fee[] fees;
}
bytes32 public TYPEHASH = keccak256("Request(address to,address from, Fee[] fees)");
constructor() EIP712("SignatureChecker", "1") {}
function verify(
Request calldata request,
bytes calldata signature,
address supposedSigner
) external view returns (bool) {
return recoverAddress(request, signature) == supposedSigner;
}
function recoverAddress(
Request calldata request,
bytes calldata signature
) public view returns (address) {
return _hashTypedDataV4(keccak256(encodeRequest(request))).recover(signature);
}
function encodeRequest(Request calldata request) public view returns (bytes memory) {
return abi.encode(TYPEHASH, request.to, request.from, request.fees);
}
}
I just want to make sure that I am encoding the request correctly in the encodeRequest function. Unfortunately I could not find anything on how to create a typehash of a nested struct. Is the way I am creating the typehash correct?
When I tried out the verify function without the fees property and the different TYPEHASH without the fee, it worked completely fine. However when I try to retrieve the address of a signature of the request struct with the fees array, it returns a wrong address.
I have also seen an example where someone tried to do this:
bytes32 public constant TYPEHASH = keccak256("Request(address to,address from, Fee[] fees)Fee(address recipient, uint256 value)");
Unfortunately it also produces a wrong address.

After doing quite a lot of research (including reading the entire EIP-712), I could craft a solution, which works:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "#openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "#openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract SignatureChecker is EIP712 {
using ECDSA for bytes32;
struct Fee {
address recipient;
uint256 value;
}
struct Request {
address to;
address from;
Fee[] fees;
}
bytes32 public constant FEE_TYPEHASH = keccak256("Fee(address recipient,uint256 value)");
bytes32 public constant REQUEST_TYPEHASH =
keccak256(
"Request(address to,address from,Fee[] fees)Fee(address recipient,uint256 value)"
);
constructor() EIP712("SignatureChecker", "1") {}
function verify(
Request calldata request,
bytes calldata signature,
address signer
) external view returns (bool) {
return recoverAddressOfRequest(request, signature) == signer;
}
function recoverAddressOfRequest(
Request calldata request,
bytes calldata signature
) public view returns (address) {
return _hashTypedDataV4(keccak256(encodeRequest(request))).recover(signature);
}
function recoverAddressOfFee(
Fee calldata fee,
bytes calldata signature
) public view returns (address) {
return _hashTypedDataV4(keccak256(encodeFee(fee))).recover(signature);
}
function encodeFee(Fee calldata fee) public pure returns (bytes memory) {
return abi.encode(FEE_TYPEHASH, fee.recipient, fee.value);
}
function encodeRequest(Request calldata request) public pure returns (bytes memory) {
bytes32[] memory encodedFees = new bytes32[](request.fees.length);
for (uint256 i = 0; i < request.fees.length; i++) {
encodedFees[i] = keccak256(encodeFee(request.fees[i]));
}
return
abi.encode(
REQUEST_TYPEHASH,
request.to,
request.from,
keccak256(abi.encodePacked(encodedFees))
);
}
}
The main problem was, that in order for this to work, you have to encode the every Fee element inside of the Request struct individually, and hash the resulting array to append it to the encoded request.

Related

Solidity how to validate calldata decodes to a paticular struct

I have an interface in solidity which looks like so, I would like my call to revert if the calldata passed isn't of a specific type
// Resolver interface
interface IResolver {
// Pass payment info as calldata only the manager should have the right to update it
function resolve(uint256 amount, ResolverOptions calldata resolverOptions) external returns (uint256);
// Reverts if the calldata passes is not a proper struct
function validateAdditionalCalldata(bytes calldata additionalCalldata) external view;
}
I've created a class to implement this here:
struct fooResolverOptions {
address[] fooAddresses;
uint256[] fooAmounts;
}
contract FooResolver is IResolver {
// Validate the additional calldata passed to the resolver contract
function validateAdditionalCalldata(bytes calldata additionalCalldata) view external {
// Convert the additional calldata to bytes memory
bytes memory additionalCalldataMemory = additionalCalldata;
// Decode the additional calldata as a FooResolverOptions struct
FooResolverOptions memory fooOptions;
bool success = abi.decode(additionalCalldataMemory, fooOptions);
// Check if the decode was successful
require(success, "Invalid additional calldata");
}
}
None of the Way's I've tried to decode work:
bool success = abi.decode(additionalCalldataMemory, fooOptions);
this way claims there is no return value from decode.
FooResolverOptions memory fooOptions;
abi.decode(additionalCalldata, fooOptions);
This way claims it wants a tuple of types. How do I decode a struct data, and validate it succeeded?
Solidity currently (v0.8) doesn't support dynamic arguments in abi.decode(), so you'll need to write logic that validates against predefined set of types.
bytes calldata additionalCalldata in your example is an array of bytes, so abi.decode(additionalCalldataMemory, <types>); tries to decode the binary to whatever <types> you pass. If the input fits the type length, it will simply decode the value to the type.
Example where the value fits into both bool and address types, so both operations succeed:
function validateAdditionalCalldata() pure external returns (bool, address) {
bytes memory additionalCalldataMemory = hex"0000000000000000000000000000000000000000000000000000000000000001";
bool decoded1 = abi.decode(additionalCalldataMemory, (bool));
address decoded2 = abi.decode(additionalCalldataMemory, (address));
return (decoded1, decoded2);
}
When the value doesn't fit the type, it throws an exception. Uncaught exception effectively reverts the transaction or the call. However, you can use try / catch to catch the exception.
pragma solidity ^0.8;
contract FooResolver {
function validateAdditionalCalldata() external view returns (bool, address) {
// does not fit `bool` but still fits `address`
bytes memory additionalCalldataMemory = hex"0000000000000000000000000000000000000000000000000000000000000002";
bool decoded1;
try this.decodeToBool(additionalCalldataMemory) returns (bool decodedValue) {
decoded1 = decodedValue;
} catch {
decoded1 = false;
}
address decoded2 = abi.decode(additionalCalldataMemory, (address));
return (decoded1, decoded2);
}
// workaround - try/catch can be currently (v0.8) used on external function calls - but not on native function calls
function decodeToBool(bytes memory data) external pure returns (bool) {
return abi.decode(data, (bool));
}
}

Signature from array of tokenId's isn't verifying

The domain message is:
struct Offer{
address from;
address to;
uint256 nonce;
uint256[] tokenIds;
uint256 duration;
}
bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
bytes32 constant OFFER_TYPEHASH = keccak256(
"Offer(address from,address to,uint256 nonce,uint256[] memory tokenIds,uint256 duration)"
);
function hash(Offer memory offer) public pure returns (bytes32) {
return keccak256(abi.encode(
OFFER_TYPEHASH,
offer.from,
offer.to,
offer.nonce,
keccak256(abi.encodePacked(offer.tokenIds)),
offer.duration
));
}
function verify(Offer memory offer, uint8 v, bytes32 r, bytes32 s) public view returns (bool) {
// Note: we need to use encodePacked here instead of encode.
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
hash(offer)
));
return ecrecover(digest, v, r, s) == ExpectedAddress;
}
I can't resolve issue with array when doing v4 signature.
If I removed tokenIds array from Offer struct, and OfferTypeHash, and also from hash func: everything works well as expected, I can verify signature via web3 and contract.
When I put tokenIds array to struct, I can't verify signature.
I googled and tried a lot of examples like:
I use this for hashing the offer:
keccak256(abi.encodePacked(array))
function hash(Offer memory offer) public pure returns (bytes32) {
return keccak256(abi.encode(
OFFER_TYPEHASH,
offer.from,
offer.to,
offer.nonce,
keccak256(abi.encodePacked(offer.tokenIds)),
offer.duration
));
}
keccak256(abi.encode(array))
keccak256(abi.encodePacked(array))
function hash(Offer memory offer) public pure returns (bytes32) {
return keccak256(abi.encode(
OFFER_TYPEHASH,
offer.from,
offer.to,
offer.nonce,
keccak256(abi.encode(offer.tokenIds)),
offer.duration
));
}
Even without Keccak:
function hash(Offer memory offer) public pure returns (bytes32) {
return keccak256(abi.encode(
OFFER_TYPEHASH,
offer.from,
offer.to,
offer.nonce,
keccak256(abi.encode(offer.tokenIds)),
offer.duration
));
}
I can't figure out: why keccak256(abi.encode is not doing the job?

Solidity CRUD trying to call All data return instead of one by one using ID

I have created a solidity that record data CRUD like where I can create read update delete but I want to create a readAll function wondering how can I write in solidity since I write like below it does not work. For calling with id it return the correct but not readAll. Looking forward for your help <3
example
function readAllTask() public view returns (uint, uint256, uint256) {
return (tasks.id, tasks.stackAddress, tasks.nftId); <============= return everything
}
pragma solidity ^0.8.6;
contract RecordData {
struct Task {
uint id;
uint256 stackAddress;
uint256 nftId;
}
Task[] tasks;
uint nextId; // default value 0, add public to see the value
function createTask(uint256 _stackAddress, uint256 _nftId) public {
tasks.push(Task(nextId, _stackAddress, _nftId));
nextId++;
}
function findIndex(uint _id) internal view returns (uint) {
for (uint i = 0; i < tasks.length; i++) {
if (tasks[i].id == _id) {
return i;
}
}
revert("Task not found");
}
function updateTask(uint _id, uint256 _stackAddress, uint256 _nftId) public {
uint index = findIndex(_id);
tasks[index].stackAddress = _stackAddress;
tasks[index].nftId = _nftId;
}
function readTask(uint _id) public view returns (uint, uint256, uint256) {
uint index = findIndex(_id);
return (tasks[index].id, tasks[index].stackAddress, tasks[index].nftId);
}
function deleteTask(uint _id) public {
uint index = findIndex(_id);
delete tasks[index];
}
}
You can return an array of structs:
function readAllTask() public view returns (Task[] memory) {
return tasks;
}
I think you have reason to do it, but just a remind that saving the data into blockchain is not a good deal, due to the high cost.
If you would like to return all the task, then simply make the tasks public. Solidity automatically assign a get function for it.
If you would like to get some specific content of task struct, then consider something like this:
function readAllTask() public view returns (uint, uint256, uint256 [] memory) {
// something
}

Return value from a deployed smart contract, via a smart contract, to a smart contract

I am trying to return a value using a function of a deployed smart contract on the blockchain.
pragma solidity 0.6.2;
contract Caller {
address cont;
function changeAdd(address _change) public {
cont = _change;
}
function caller (bytes memory test) public returns(bool, bytes memory) {
bytes memory payload = abi.encodeWithSignature("callMe(bytes)", test);
(bool success, bytes memory result)= address(cont).call(payload);
return (success, (result));
}
function viewCont() public view returns(address) {
return cont;
}
}
And the deployed test contract is this:
pragma solidity 0.6.2;
contract Store {
uint counter;
function callMe(bytes calldata test) external returns(bytes memory) {
counter++;
return abi.encode(test);
}
function viewCounter () public view returns(uint256) {
return(counter);
}
function clearCounter() public {
counter = 0 ;
}
}
In this example, I am deploying contract "Store" on the blockchain where it is assigned a random address, which can be pointed at via the Caller.changeAdd function in order to use Caller.caller function. Thus, I am trying to send data in the form of bytes to the Store contract, which is supposed to send back the same data to the Caller contract. I am trying to isolate -only- the data that is originally sent, so I can use it to test interaction between smart contracts on the blockchain. I tried in the beginning to send an integer, but I couldn't find a way to do it. So I used bytes and it worked, but still the data I receive isn't the same that I send in the first place(e.g. I send 0x0 and I receive a big bytes number, which isn't the same as 0x0).
I could appreciate any help on how to receive and handle data between two different, unlinked smart contracts, thank you in advance.
Do you try to use abi.decode function of Solidity.
In the below example, contract B calls setName of contract A, then decodes the result by using abi.decode function.
contract A {
string public name;
constructor(string memory tokenName) public {
name = tokenName;
}
function setName(string memory newName) public returns ( string memory){
name = newName;
return newName;
}
}
contract B {
event Log(string msg);
string public myName;
function call(address addr, string memory newName) public {
bytes memory payload = abi.encodeWithSignature("setName(string)", newName);
(bool success, bytes memory result)= addr.call(payload);
// Decode data
string memory name = abi.decode(result, (string));
myName = name;
emit Log(name);
}
}

How to encode tuple as input parameter to function using web3j

I am attempting to call a solidity function that looks something like the following:
function fillOrder(
Order memory order,
uint256 takerAssetFillAmount,
bytes memory signature
)
Using web3j I would create the function similar to below, however I'm not quite sure how to represent the order which is represented as a struct in Solidity.
List<Type> inputParams = Arrays.asList(???, new
Uint256(takerAssetFillAmount), new Bytes32(signture));
new Function("fillOrder", inputParams, Collections.emptyList());
Any pointers on how I should represent the struct?
Thanks.
You can wrap parameters with square brackets.
For example, let's say I have a contract:
contract Test {
struct Foo {
uint a;
string b;
address c;
}
function bar (Foo memory foo) public {
c = foo.c;
}
}
I can call bar function with web3.js like this:
contract.methods.foo([123, "123", "0xABC...."]).send({ from: '0x...' })
here is the contract address https://goerli.etherscan.io/address/0xd5999bf0ce31a1d9d6a6de2bf03feaff1913cee5#writeContract
in the write function , createSwapOrder is asking nested Tuple . here is the solidity code to show the structure of tuple :
struct Side {
address user;
bytes signedRequiredOutput;
ERC20Component[] erc20s;
ERC721Component[] erc721s;
ERC1155Component[] erc1155s;
}
struct ERC20Component {
uint256 amount;
address underlying;
// A signed approval transaction giving `amount` transfer rights
// of token `underlying` to address(this).
// bytes signedApproval;
}
struct ERC721Component {
uint256 tokenId;
address collection;
// A signed approval transaction giving `tokenId` tranfer rights
// of token `collection` to address(this).
// bytes signedApproval;
}
struct ERC1155Component {
uint256 tokenId;
uint256 amount;
address collection;
// A signed approval transaction giving `tokenId` tranfer rights
// of token `collection` to address(this).
// bytes signedApproval;
}
struct Order {
Side side0;
Side side1;
uint256 expiry;
bytes32 hashlock;
bytes32 preimage;
bool completed;
}
event OrderCreated(address indexed user, bytes32 orderId);
uint256 public totalOrders;
mapping(bytes32 => Order) public orders;
function createSwapOrder(
Side calldata side0,
bytes32 hashlock,
uint256 timelock
) public {
...
}
and in first args side0 is asking a nested tuple and this tuple formet should be like this
["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0x00",[["32","0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"]],[["32","0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"]],[["32","32","0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"]]],
i hope you can understand the structure how its provided !! and sure it working
Web3j offers such classes as StaticStruct and DynamicStruct where you define your struct object via primitives. Here is the sample from my project:
class Value: DynamicStruct {
private lateinit var offer: String
private lateinit var availableSince: BigInteger
private lateinit var availabilityEnd: BigInteger
private var isConsumed: Boolean = false
private lateinit var lockedUntil: BigInteger
constructor(
offer: String,
availableSince: BigInteger,
availabilityEnd: BigInteger,
isConsumed: Boolean,
lockedUntil: BigInteger
) : super(
Utf8String(offer), Uint256(availableSince),
Uint256(availabilityEnd), Bool(isConsumed),
Uint256(lockedUntil)
) {
this.offer = offer
this.availableSince = availableSince
this.availabilityEnd = availabilityEnd
this.isConsumed = isConsumed
this.lockedUntil = lockedUntil
}
constructor(
offer: Utf8String,
availableSince: Uint256,
availabilityEnd: Uint256,
isConsumed: Bool,
lockedUntil: Uint256
) : super(offer, availableSince, availabilityEnd, isConsumed, lockedUntil) {
this.offer = offer.value
this.availableSince = availableSince.value
this.availabilityEnd = availabilityEnd.value
this.isConsumed = isConsumed.value
this.lockedUntil = lockedUntil.value
}
}
Ideally you just need to pass this struct instance to you contract method as a parameter where contract is autogenerated over $web3j solidity generate -b /path/to/<smart-contract>.bin -a /path/to/<smart-contract>.abi -o /path/to/src/main/java -p com.your.organisation.name command.
However personally I faced with issue to make this console command working and have to implement required logic by my own. This is in the progress now.