Created
October 1, 2022 17:19
-
-
Save azimin/66fd6d9e5616b61c9a3977f5c2653d4c to your computer and use it in GitHub Desktop.
Ability to generate signature for solidity tests in foundry
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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