Skip to content

Instantly share code, notes, and snippets.

@azimin
Created October 1, 2022 17:19
Show Gist options
  • Save azimin/66fd6d9e5616b61c9a3977f5c2653d4c to your computer and use it in GitHub Desktop.
Save azimin/66fd6d9e5616b61c9a3977f5c2653d4c to your computer and use it in GitHub Desktop.
Ability to generate signature for solidity tests in foundry
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "forge-std/Test.sol";
import {ERC721} from "solmate/tokens/ERC721.sol";
import {ECDSA} from "openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract ERC721AirdropWithSign is ERC721 {
address public signer;
constructor() ERC721('XX', 'XX') { }
function setSigner(address signer_) external {
signer = signer_;
}
function testSign(bytes calldata signature)
external
payable
{
if (!_verify(signer, _hash(msg.sender), signature))
revert Errors.WrongSignature();
}
function _verify(
address signer,
bytes32 hash,
bytes calldata signature
) private pure returns (bool) {
return signer == ECDSA.recover(hash, signature);
}
function _hash(address account) private pure returns (bytes32) {
return
ECDSA.toEthSignedMessageHash(keccak256(abi.encodePacked(account)));
}
}
contract TestExample is Test {
SignUtilities internal signUtilities;
ERC721AirdropWithSign public sourceSign;
function setUp() public {
signUtilities = new SignUtilities();
sourceSign = new ERC721AirdropWithSign();
}
function testSign() public {
address user1 = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
(address addr, uint256 privateKey) = makeAddrAndKey('user1');
sourceSign.setSigner(addr);
bytes memory signature = signUtilities.signMessage(privateKey, _hash(user1));
vm.startPrank(user1);
sourceSign.testSign(signature);
}
function testExternalSign() public {
address user1 = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
vm.startPrank(user1);
sourceSign.testSign(signUtilities.bytesSignatureFromString("0x22e9287d368ad4d3be46f116ddbdae12794c09cfc367e6ccb522dcc559b0f644692598fde17fa1f927fe11123bcae46ed404802b559b59a4e8aad9127824d1971b"));
}
function _hash(address account) private pure returns (bytes32) {
return
ECDSA.toEthSignedMessageHash(keccak256(abi.encodePacked(account)));
}
}
contract SignUtilities is Test {
function signMessage(uint256 privateKey, bytes32 hash)
external
returns (bytes memory)
{
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, hash);
return makeSignatureFromVRS(v, r, s);
}
function makeSignatureFromVRS(uint8 v, bytes32 r, bytes32 s) public pure returns (bytes memory) {
bytes memory sig = new bytes(65);
assembly {
mstore(add(sig, 65), v)
mstore(add(sig, 32), r)
mstore(add(sig, 64), s)
}
return sig;
}
// Useful if you have signature (for example from web2 JS call) string (135 in length) and want to convert it to bytes
function bytesSignatureFromString(string memory s) public pure returns (bytes memory) {
bytes memory ss = bytes(s);
require(ss.length % 2 == 0);
bytes memory r = new bytes(ss.length/2 - 1);
for (uint i=0; i<(ss.length/2 - 1); ++i) {
uint sI = i + 1;
r[i] = bytes1(fromHexChar(uint8(ss[2*sI])) * 16 + fromHexChar(uint8(ss[2*sI+1])));
}
return r;
}
// Helper for bytesSignatureFromString
function fromHexChar(uint8 c) private pure returns (uint8) {
if (bytes1(c) >= bytes1('0') && bytes1(c) <= bytes1('9')) {
return c - uint8(bytes1('0'));
}
if (bytes1(c) >= bytes1('a') && bytes1(c) <= bytes1('f')) {
return 10 + c - uint8(bytes1('a'));
}
if (bytes1(c) >= bytes1('A') && bytes1(c) <= bytes1('F')) {
return 10 + c - uint8(bytes1('A'));
}
revert("fail");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment