Когда я разбирался с документацией Foundry, то видел там раздел по написанию тестов для подписей стандарта EIP712.
Долгое время все никак не получалось подступиться к ним, и вот пришло время разобраться в этой теме.
Вот сама ссылка на документацию:
https://book.getfoundry.sh/tutorials/testing-eip712
А сейчас мы быстро пробежимся по коду.
Итак, для написания тестов в данном случае мы будем работать с токеном ERC20 от компании Solmate, так как там уже по умолчанию идет функция permit().
https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol
Нам также потребуются еще два контракта: это MockToken, который будет наследовать от ERC20 и SigUtils, который поможет правильно работать с подписями.
Наш токен предельно простой:
contract MyToken is ERC20 {
address public owner;
constructor(address newOwner) ERC20("token", "MTK", 18) {}
function mint(address user, uint256 amount) public {
_mint(user, amount);
}
}SigUtils чуть более длинный:
contract SigUtils {
bytes32 internal DOMAIN_SEPARATOR;
constructor(bytes32 _DOMAIN_SEPARATOR) {
DOMAIN_SEPARATOR = _DOMAIN_SEPARATOR;
}
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
struct Permit {
address owner;
address spender;
uint256 value;
uint256 nonce;
uint256 deadline;
}
function getStructHash(Permit memory _permit)
internal
pure
returns (bytes32)
{
return
keccak256(
abi.encode(
PERMIT_TYPEHASH,
_permit.owner,
_permit.spender,
_permit.value,
_permit.nonce,
_permit.deadline
)
);
}
function getTypedDataHash(Permit memory _permit)
public
view
returns (bytes32)
{
return
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
getStructHash(_permit)
)
);
}
}Все просто: мы создали структуру подписи подобную ERC20 permit() и две функции для хеширования структуры подписи и цельных данных.
И теперь мы готовы писать тесты для нашего токена.
Для начала нам потребуется в настройках развернуть контракты MyToken и SigUtils, создать приватные ключи и на их основе сгенерировать адреса пользователей, так как сами подписи нужно создавать с использованием приватного ключа.
function setUp() public {
token = new MyToken(address(this));
sigUtils = new SigUtils(token.DOMAIN_SEPARATOR());
ownerPrivateKey = 0xA11CE;
spenderPrivateKey = 0xB0B;
owner = vm.addr(ownerPrivateKey);
spender = vm.addr(spenderPrivateKey);
token.mint(owner, 1e18);
}После этого можно написать первый тест для функции permit():
function test_Permit() public {
SigUtils.Permit memory permit = SigUtils.Permit({
owner: owner,
spender: spender,
value: 1e18,
nonce: 0,
deadline: 1 days
});
bytes32 digest = sigUtils.getTypedDataHash(permit);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest);
token.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v,
r,
s
);
assertEq(token.allowance(owner, spender), 1e18);
assertEq(token.nonces(owner), 1);
}Тут мы создаем структуры с помощью контракта SigUtils, хешируем и подписываем с помощью приватного ключа, затем уже вызываем функцию permit() и делаем проверки на nonce и allowance.
В файле примере, который вы найдете в конце поста, можно посмотреть тесты на: срок действия подписи, невалидного signer и nonce, а также на signature replay.