I have two functions on contract:
function 1 receives bytes params variable
function foo1(bytes memory params)
function 2 receives two params: address param1, uint256 param2
function foo2(address param1, uint256 param2)
my goals somehow pass params as bytes to foo1, decode it on function, and pass decoded params to foo2
like:
function foo2(address param1, uint256 param2) {
// do something
}
function decode(bytes params) private returns(???){
// decode
}
function foo1(bytes params) public {
var decodedParams = fromBytes(params)
foo2(decodedParams.param1, decodedParams.param2)
}
from frontend I expect something like:
const params = toBytes({param1: '0xAddRess', param2: 1})
myContract.foo1(params)
You can use abi.encodeParameters() on frontend
const bytesHex = web3.eth.abi.encodeParameters(
['address', 'uint256'],
['0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe', '1']
);
and abi.decode() in the contract
pragma solidity ^0.8;
contract MyContract {
function foo2(address param1, uint256 param2) external {
// do something
}
function foo1(bytes memory params) external {
(address decodedAddress, uint256 decodedUint) = abi.decode(params, (address, uint256));
this.foo2(decodedAddress, decodedUint);
}
}
Related
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.
I am trying to create a test around a contract, but I am having problems understanding how to sign it in a test environment
This is my contract
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.9 <0.9.0;
import "#divergencetech/ethier/contracts/crypto/SignatureChecker.sol";
import "#divergencetech/ethier/contracts/crypto/SignerManager.sol";
import "#divergencetech/ethier/contracts/erc721/BaseTokenURI.sol";
import "#divergencetech/ethier/contracts/erc721/ERC721ACommon.sol";
import "#divergencetech/ethier/contracts/erc721/ERC721Redeemer.sol";
import "#divergencetech/ethier/contracts/sales/FixedPriceSeller.sol";
import "#divergencetech/ethier/contracts/utils/Monotonic.sol";
import "#openzeppelin/contracts/token/common/ERC2981.sol";
import "#openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "#openzeppelin/contracts/utils/structs/EnumerableSet.sol";
interface ITokenURIGenerator {
function tokenURI(uint256 tokenId) external view returns (string memory);
}
// #author divergence.xyz
contract TestBirds is
ERC721ACommon,
BaseTokenURI,
FixedPriceSeller,
ERC2981,
AccessControlEnumerable
{
using EnumerableSet for EnumerableSet.AddressSet;
using ERC721Redeemer for ERC721Redeemer.Claims;
using Monotonic for Monotonic.Increaser;
/**
#notice Role of administrative users allowed to expel a Player from the
mission.
#dev See expelFromMission().
*/
bytes32 public constant EXPULSION_ROLE = keccak256("EXPULSION_ROLE");
constructor(
string memory name,
string memory symbol,
string memory baseTokenURI,
address payable beneficiary,
address payable royaltyReceiver
)
ERC721ACommon(name, symbol)
BaseTokenURI(baseTokenURI)
FixedPriceSeller(
2.5 ether,
Seller.SellerConfig({
totalInventory: 10_000,
lockTotalInventory: true,
maxPerAddress: 0,
maxPerTx: 0,
freeQuota: 125,
lockFreeQuota: false,
reserveFreeQuota: true
}),
beneficiary
)
{
_setDefaultRoyalty(royaltyReceiver, 1000);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
/**
#dev Mint tokens purchased via the Seller.
*/
function _handlePurchase(
address to,
uint256 n,
bool
) internal override {
_safeMint(to, n);
}
/**
#dev Record of already-used signatures.
*/
mapping(bytes32 => bool) public usedMessages;
/**
#notice Mint tokens.
*/
function mintPublic(
address to,
bytes32 nonce,
bytes calldata sig
) external payable {
signers.requireValidSignature(
signaturePayload(to, nonce),
sig,
usedMessages
);
_purchase(to, 1);
}
function alreadyMinted(address to, bytes32 nonce)
external
view
returns (bool)
{
return
usedMessages[
SignatureChecker.generateMessage(signaturePayload(to, nonce))
];
}
/**
#dev Constructs the buffer that is hashed for validation with a minting
signature.
*/
function signaturePayload(address to, bytes32 nonce)
internal
pure
returns (bytes memory)
{
return abi.encodePacked(to, nonce);
}
/**
#dev Required override to select the correct baseTokenURI.
*/
function _baseURI()
internal
view
override(BaseTokenURI, ERC721A)
returns (string memory)
{
return BaseTokenURI._baseURI();
}
/**
#notice If set, contract to which tokenURI() calls are proxied.
*/
ITokenURIGenerator public renderingContract;
/**
#notice Sets the optional tokenURI override contract.
*/
function setRenderingContract(ITokenURIGenerator _contract)
external
onlyOwner
{
renderingContract = _contract;
}
/**
#notice If renderingContract is set then returns its tokenURI(tokenId)
return value, otherwise returns the standard baseTokenURI + tokenId.
*/
function tokenURI(uint256 tokenId)
public
view
override
returns (string memory)
{
if (address(renderingContract) != address(0)) {
return renderingContract.tokenURI(tokenId);
}
return super.tokenURI(tokenId);
}
/**
#notice Sets the contract-wide royalty info.
*/
function setRoyaltyInfo(address receiver, uint96 feeBasisPoints)
external
onlyOwner
{
_setDefaultRoyalty(receiver, feeBasisPoints);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721ACommon, ERC2981, AccessControlEnumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
It compiles ok, but when I tried to create a test for minting, I must generate a valid signature...
This is the test
const { expect } = require('chai');
describe("TestBirds", function () {
it ("Should return correct name, URI, owner and beneficiary", async function () {
const [owner, addr1] = await hre.ethers.getSigners()
provider = ethers.provider
const TestBirdsContract = await hre.ethers.getContractFactory("TestBirds")
const testBirdsContractDeployed = await TestBirdsContract.deploy(
"TestBirds",
"APFP",
"https://test.url/",
owner.address,
owner.address)
console.log(await provider.getBalance(owner.address));
await testBirdsContractDeployed.deployed()
const nonce = await ethers.provider.getTransactionCount(owner.address, "latest")
await testBirdsContractDeployed.mintPublic(owner.address, nonce, signature???)
expect(await testBirdsContractDeployed.name()).to.equal("TestBirds")
expect(await testBirdsContractDeployed.tokenURI(0), "https://test.url/0")
expect(await testBirdsContractDeployed.ownerOf(0)).to.equal(owner.address)
})
})
How should I sing this in order to work? I can not test the contract without the sign. If I remove the signature param from the contract it works, but that is not what I want.
Thanks
It seems that the only missing was to add a signer. This seems to work for your test contract...
import { expect } from "chai";
import { ethers } from "hardhat";
describe("Test signature", function () {
it("deploy tester contract, and send signed message", async function () {
const TestSignature = await ethers.getContractFactory("TestSignature",owner);
const testSignature = await TestSignature.deploy();
await testSignature.deployed();
const [owner] = await ethers.getSigners();
// missing line
testSignature.addSigner(owner.address);
const params = ethers.utils.solidityPack(
["address", "uint256", "bytes32"],
[owner.address, "10", ethers.utils.keccak256("0x66")]
);
const signed = await owner.signMessage(params);
console.log("owner address", owner.address);
await testSignature.mint(
owner.address,
"10",
ethers.utils.keccak256("0x66"),
signed
);
});
});
I've attempted the following, but without much success yet:
import { expect } from "chai";
import { ethers } from "hardhat";
describe("Test signature", function () {
it("deploy tester contract, and send signed message", async function () {
const TestSignature = await ethers.getContractFactory("TestSignature");
const testSignature = await TestSignature.deploy();
await testSignature.deployed();
const [owner] = await ethers.getSigners();
const params = ethers.utils.solidityPack(
["address", "uint256", "bytes32"],
[owner.address, "10", ethers.utils.keccak256("0x66")]
);
const signed = await owner.signMessage(params);
console.log("owner address", owner.address);
await testSignature.mint(
owner.address,
"10",
ethers.utils.keccak256("0x66"),
signed
);
});
});
That is a test against the following contract:
pragma solidity >=0.8.0 <0.9.0;
// SPDX-License-Identifier: MIT
import "./SignatureChecker.sol";
contract TestSignature {
using EnumerableSet for EnumerableSet.AddressSet;
using SignatureChecker for EnumerableSet.AddressSet;
EnumerableSet.AddressSet internal signers;
constructor() {
signers.add(msg.sender);
}
mapping(bytes32 => bool) public usedMessages;
function mint(
address to,
uint256 price,
bytes32 nonce,
bytes calldata sig
) external payable {
signers.requireValidSignature(
signaturePayload(to, price, nonce),
sig,
usedMessages
);
}
/**
#dev Constructs the buffer that is hashed for validation with a minting signature.
*/
function signaturePayload(
address to,
uint256 price,
bytes32 nonce
) public pure returns (bytes memory) {
return abi.encodePacked(to, price, nonce);
}
function generateMessage(bytes memory data) public pure returns (bytes32) {
return SignatureChecker.generateMessage(data);
}
}
From what I understood, the signed message should match the message, and the ECDSA.toEthSignedMessageHash(data)
appends the \x19Ethereum Signed Message:\n to the beggining of the message. After it matches, it can recover the signer address, but it doesn't seem to work quite right yet.
I have a roulette smart contract, that uses another smart contract to provide it with random numbers. The issue i'm having is during compilation, i get the error:
TypeError: Type contract IRandomNumberGenerator is not implicitly convertible to expected type address.
project:/contracts/Roulette.sol:34:29:
randomNumberGenerator = IRandomNumberGenerator(randomNumberGenerator);
I'm not exactly sure where i'm going wrong, i've seen this code used in other contracts. Here is my full code, any help would be much appreciated.
// SPDX-License-Identifier: UNLICENSED"
pragma solidity ^0.8.7;
import "#openzeppelin/contracts/token/ERC20/IERC20.sol";
import "#openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "#openzeppelin/contracts/access/Ownable.sol";
import "./IRandomNumberGenerator.sol";
contract Roulette is Ownable {
using SafeERC20 for IERC20;
IERC20 public gameToken;
uint256[100] internal randomNumbers;
IRandomNumberGenerator internal randomNumberGenerator;
// ensure caller is the random number generator contract
modifier onlyRandomGenerator() {
require(msg.sender == address(randomNumberGenerator), "Only random generator");
_;
}
constructor(address tokenAddress) {
gameToken = IERC20(tokenAddress);
}
function setRandomNumberGenerator(address randomNumberGenerator) external onlyOwner {
randomNumberGenerator = IRandomNumberGenerator(randomNumberGenerator);
}
function getRandomNumber() internal onlyOwner returns (uint) {
uint result = randomNumbers[randomNumbers.length-1];
delete randomNumbers[randomNumbers.length-1];
return result;
}
function numberGenerated(uint randomNumber) external onlyRandomGenerator {
randomNumbers = expand(randomNumber);
}
// generate 100 random numbers from the random number seed
function expand(uint256 randomValue) public pure returns (uint256[] memory expandedValues) {
expandedValues = new uint256[](100);
for (uint256 i = 0; i < 100; i++) {
expandedValues[i] = uint256(keccak256(abi.encode(randomValue, i)));
}
return expandedValues;
// TODO - ensure random numbers are roulette numbers
}
}
// SPDX-License-Identifier: UNLICENSED"
pragma solidity ^0.8.7;
import "#chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
import "./IRoulette.sol";
contract RandomNumberGenerator is VRFConsumerBase {
address public roulette;
bytes32 internal keyHash;
uint256 internal fee;
uint256 internal randomResult;
// modifier to check if caller of owner is the admin
modifier onlyRoulette() {
require(msg.sender == roulette, "Caller to function is not the roulette contract");
_;
}
constructor(address _roulette)
VRFConsumerBase(
0xa555fC018435bef5A13C6c6870a9d4C11DEC329C, // VRF Coordinator
0x84b9B910527Ad5C03A9Ca831909E21e236EA7b06 // LINK Token
)
{
keyHash = 0xcaf3c3727e033261d383b315559476f48034c13b18f8cafed4d871abe5049186;
fee = 0.1 * 10 ** 18;
roulette = _roulette;
}
function getRandomNumber() public onlyRoulette returns (bytes32 requestId) {
require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");
return requestRandomness(keyHash, fee);
}
function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
randomResult = randomness;
IRoulette(roulette).numberGenerated(randomResult);
}
}
Found out the issue was having the same name for both the parameter and the variable.
Updated the function to:
function setRandomNumberGenerator(address _randomNumberGenerator) external onlyOwner {
randomNumberGenerator = IRandomNumberGenerator(_randomNumberGenerator);
}
I am getting this error in testing my upgradeable smart contract.
I am really stuck with this can anyone help me get out of this?
contracts/StakingContract.sol
//SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "#openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol";
import "#openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "#openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "#openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "#openzeppelin/contracts-upgradeable/token/ERC20/SafeERC20Upgradeable.sol";
import "#openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "#openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "#openzeppelin/contracts-upgradeable/proxy/Initializable.sol";
contract StakingContract is ERC20Upgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable {
using SafeMathUpgradeable for uint256;
using AddressUpgradeable for address;
using SafeERC20Upgradeable for IERC20Upgradeable;
event TokensStaked(address _tokenStaker, uint256 _amount, uint256 unlockTime);
event TokenUnstaked(address _tokenStaker, uint256 _amount);
// Variable that prevents _deposit method from being called 2 times
bool private locked;
// The total staked amount
uint256 public totalStaked;
mapping (address => uint256) public stakeBalances;
mapping(address => uint256) private timestamp;
function initialize(uint256 initialSupply) public initializer {
__ERC20_init("MyToken", "TKN");
_mint(owner(), initialSupply);
}
function stakeTokens(address token, uint amount) external {
stakeBalances[msg.sender] = stakeBalances[msg.sender].add(amount);
timestamp[msg.sender] = block.timestamp.add(30 days);
require(IERC20Upgradeable(token).allowance(msg.sender, address(this)) >= amount, "Check the token allowance");
require(IERC20Upgradeable(token).transferFrom(msg.sender, address(this), amount), "Transfer failed!");
totalStaked = totalStaked.add(amount);
emit TokensStaked(msg.sender, amount, timestamp[msg.sender]);
}
function unstakeTokens(address token, uint256 amount) external nonReentrant() {
require(amount <= stakeBalances[msg.sender], "Sorry! You don't have sufficient stake balance.");
require(block.timestamp >= timestamp[msg.sender], "Sorry! you cannot withdraw tokens before your stake time.");
stakeBalances[msg.sender] = stakeBalances[msg.sender].sub(amount);
require(IERC20Upgradeable(token).transfer(msg.sender, amount));
totalStaked = totalStaked.sub(amount);
emit TokenUnstaked(msg.sender, amount);
}
function ownerWithdraw(address token) external onlyOwner() nonReentrant() {
require(IERC20Upgradeable(token).transfer(owner(), totalStaked), "Transfer Failed!");
uint256 ownerStakedBalance = stakeBalances[msg.sender];
stakeBalances[msg.sender] = stakeBalances[msg.sender].sub(ownerStakedBalance);
totalStaked = totalStaked.sub(totalStaked);
}
}
test/StakingContract.test.js
const { accounts, contract, web3 } = require('#openzeppelin/test-environment');
const { expect } = require('chai');
const { TestHelper } = require('#openzeppelin/cli');
const { Contracts, ZWeb3 } = require('#openzeppelin/upgrades');
// Import utilities from Test Helpers
const { BN, expectEvent, expectRevert } = require('#openzeppelin/test-helpers');
ZWeb3.initialize(web3.currentProvider);
const Box = Contracts.getFromLocal('StakingContract');
describe('StakingContract', function () {
const [ owner, other ] = accounts;
beforeEach(async function () {
this.project = await TestHelper();
this.proxy = await this.project.createProxy(Box);
});
it('retrieve returns a value previously stored', async function () {
// Use large integer comparisons
expect(await this.proxy.methods.owner().call()).to.equal(owner);
});
});
it gives the following error:
0 passing (864ms)
1 failing
1) StakingContract
retrieve returns a value previously stored:
AssertionError: expected '0x0000000000000000000000000000000000000000' to equal '0xA05c7D52b924DceB23a766ccB1e91e67b4aCF014'
+ expected - actual
-0x0000000000000000000000000000000000000000
+0xA05c7D52b924DceB23a766ccB1e91e67b4aCF014
I have tried to fix it with other things but the still issue persists.
Thanks in advance.
I have a contract which holds tokens on behalf of addresses and transfers them when a signed hash of a transfer is provided.
The contract looks like this
pragma solidity ^0.5.0;
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
contract Settlement is Ownable {
using SafeMath for uint256;
struct Withdrawal {
uint256 amount;
address token;
uint256 timestamp;
}
// token => (holder => balance)
mapping(address => mapping(address => uint256)) public tokenBalances;
mapping(address => Withdrawal) withdrawals;
function transferInto(address recipient, uint256 amount, address token) public {
//get the tokens
IERC20(token).transferFrom(msg.sender, address(this), amount);
//increase the token balance in the payment contract
tokenBalances[token][recipient] = tokenBalances[token][recipient].add(amount);
}
string constant private prefix = "\u0019Ethereum Signed Message:\n32";
function transfer(address to, uint256 amount, address token, uint8 v, bytes32 r, bytes32 s)
public {
bytes32 paramHash = keccak256(abi.encodePacked(to, amount, token));
address signer = ecrecover(keccak256(abi.encodePacked(prefix, paramHash)), v, r, s);
//SafeMath ensures that the signer has enough tokens in their payment account
tokenBalances[token][signer] = tokenBalances[token][signer].sub(amount);
IERC20(token).transfer(to, amount);
}
}
and I have written a function to create the signature which is passed to the transfer function of the contract:
const ethers = require('ethers')
const BigNumber = require('bignumber.js')
const utils = require('web3-utils')
// the purpose of this function is to be able to create BN from exponent numbers like '2e22' they must be formatted as string in this case
const toBN = (num) => utils.toBN(new BigNumber(num).toString(10))
async function signTransfer(recipient, amount, tokenAddress, privateKey){
const wallet = new ethers.Wallet(privateKey)
const txMsg = utils.soliditySha3(recipient, toBN(amount), tokenAddress)
const messageHashBytes = ethers.utils.arrayify(txMsg)
const flatSig = await wallet.signMessage(messageHashBytes)
const sig = ethers.utils.splitSignature(flatSig)
return {
...sig,
hash: messageHashBytes
}
}
module.exports = signTransfer
this works but I had to use both ethers and web3-utils packages to implement this.
how can I replace the soliditySha3 function with an ethers version?
I had a look at the implementation of soliditySha3 and it looks mighty complex.
The thing is that web3js does not seem to have a function to create the messageHashBytes in my function. So I'm stuck with both. It's not super bad, but it would be nice to reduce the number of libraries.
If you're okay just using web3.js for everything, something like this should work:
function signTransfer(recipient, amount, tokenAddress, privateKey) {
return web3.eth.accounts.sign(
web3.utils.soliditySha3(recipient, toBN(amount), tokenAddress),
privateKey);
}