From 6e93cc1313b6053294441fab84e8c3470b928a44 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Mon, 18 Jul 2022 17:59:20 -0700 Subject: [PATCH 01/27] Copy over from huff-examples --- lib/forge-std | 2 +- lib/foundry-huff | 2 +- lib/solmate | 2 +- src/tokens/ERC721.huff | 354 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 src/tokens/ERC721.huff diff --git a/lib/forge-std b/lib/forge-std index be5c649c..27e14b7f 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit be5c649c897519cdae29c5a0ad00d4c1351e4dcc +Subproject commit 27e14b7f2448e5f5ac32719f51fe652aa0b0733e diff --git a/lib/foundry-huff b/lib/foundry-huff index c22e6f72..324c51e8 160000 --- a/lib/foundry-huff +++ b/lib/foundry-huff @@ -1 +1 @@ -Subproject commit c22e6f72a1af4e0585228da9b317bd8e7a6e9b49 +Subproject commit 324c51e8c1b7b03268eb40c9d518cf8845112baf diff --git a/lib/solmate b/lib/solmate index 10fc959d..d155ee8d 160000 --- a/lib/solmate +++ b/lib/solmate @@ -1 +1 @@ -Subproject commit 10fc959d987aab45f24e592d44449c723191ba2d +Subproject commit d155ee8d58f96426f57c015b34dee8a410c1eacc diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff new file mode 100644 index 00000000..0aba0099 --- /dev/null +++ b/src/tokens/ERC721.huff @@ -0,0 +1,354 @@ + +/* Imports */ +#include "./contracts/utils/Ownable.huff" +#include "./contracts/utils/Address.huff" +#include "./contracts/utils/Utils.huff" +#include "./contracts/utils/HashMap.huff" + +/* Interface */ +#define function mint(address,uint256) nonpayable returns () + +#define function name() nonpayable returns (string) +#define function symbol() nonpayable returns (string) +#define function tokenURI(uint256) nonpayable returns (string) + +#define function mint(address,uint256) nonpayable returns () +#define function transfer(address,uint256) nonpayable returns () +#define function transferFrom(address,address,uint256) nonpayable returns () +#define function approve(address,uint256) nonpayable returns () +#define function setApprovalForAll(address,bool) nonpayable returns () + +#define function getApproved(uint256) view returns (address) +#define function isApprovedForAll(address,address) view returns (uint256) +#define function ownerOf(uint256) view returns (address) +#define function balanceOf(address) view returns (uint256) +#define function supportsInterface(bytes4) view returns (bool) + +#define event Transfer(address,address,uint256) +#define event Approval(address,address,uint256) +#define event ApprovalForAll(address,address,bool) + +/* Events Signatures */ +#define constant TRANSFER_EVENT_SIGNATURE = 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF +#define constant APPROVAL_EVENT_SIGNATURE = 0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925 +#define constant APPROVAL_FOR_ALL_EVENT_SIGNATURE = 0x17307EAB39AB6107E8899845AD3D59BD9653F200F220920489CA2B5937696C31 + +/* Storage Slots */ +#define constant NAME_LOCATION = FREE_STORAGE_POINTER() +#define constant SYMBOL_LOCATION = FREE_STORAGE_POINTER() +#define constant OWNER_LOCATION = FREE_STORAGE_POINTER() // ownerOf +#define constant BALANCE_LOCATION = FREE_STORAGE_POINTER() // balanceOf +#define constant SINGLE_APPROVAL_LOCATION = FREE_STORAGE_POINTER() // getApproved + +/* Constructor */ + +#define macro CONSTRUCTOR() = takes(0) returns (0) { + // Set msg.sender as the owner of the contract. + OWNABLE_CONSTRUCTOR() +} + +/// >>>>>>>>>>>>>>>>>>>>> VIEW FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// + +#define macro BALANCE_OF() = takes (0) returns (0) { + 0x04 calldataload // [account] + [BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance] + 0x00 mstore // [] + 0x20 0x00 return // [] +} + +#define macro OWNER_OF() = takes (0) returns (0) { + 0x04 calldataload // [tokenId] + [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner] + 0x00 mstore // [] + 0x20 0x00 return // [] +} + +#define macro IS_APPROVED_FOR_ALL() = takes (0) returns (0) { + 0x24 calldataload // [to] + 0x04 calldataload // [from, to] + LOAD_ELEMENT_FROM_KEYS(0x00) // [value] + + 0x00 mstore + 0x20 0x00 return +} + +#define macro GET_APPROVED() = takes (0) returns (0) { + 0x04 calldataload // [tokenId] + [SINGLE_APPROVAL_LOCATION] + LOAD_ELEMENT_FROM_KEYS(0x00) // [spender] + 0x00 mstore + 0x20 0x00 return +} + +#define macro NAME() = takes (0) returns (0) { + 0x00 0x00 revert +} + +#define macro SYMBOL() = takes (0) returns (0) { + 0x00 0x00 revert +} + +#define macro TOKEN_URI() = takes (0) returns (0) { + 0x00 0x00 revert +} + +#define macro SUPPORTS_INTERFACE() = takes (0) returns (0) { + // grab interfaceId + 0x04 calldataload // [interfaceId] + 0xe0 + shr + + // check if erc165 interfaceId + dup1 // [interfaceId, interfaceId] + 0x01ffc9a7 eq // [is_erc165, interfaceId] + is_interface jumpi + + // check if erc721 interfaceId + dup1 // [interfaceId, interfaceId] + 0x80ac58cd eq // [is_erc721, interfaceId] + is_interface jumpi + + // check if erc721Metadata interfaceId + 0x5b5e139f eq // [is_erc721Metadata] + is_interface jumpi + + 0x00 mstore // [] + 0x20 0x00 return // [] + + is_interface: + pop // [] + 0x01 0x00 mstore // [] + 0x20 0x00 return // [] +} + +/// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// + +/* Transfer Functions */ +#define macro TRANSFER_TAKE_FROM(error) = takes(3) returns (3) { + // input stack [from, to, tokenId] + + // check if from is the owner of the token + dup1 // [from, from, to, tokenId] + dup4 // [tokenId, from, from, to, tokenId] + [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, from, from, to, tokenId] + swap2 // [from, owner, from, to, tokenId] + eq // [from_is_owner, from, to, tokenId] + cont jumpi // [from, to, tokenId] + jump + cont: + + // check if msg.sender == from + dup1 caller // [msg.sender, from, from, to, tokenId] + eq // [from_is_msg.sender, from, to, tokenId] + is_authorized jumpi // [from, to, tokenId] + + // check if approved for all + caller dup2 // [from, msg.sender, from, to, tokenId] + LOAD_ELEMENT_FROM_KEYS(0x00) // [is_approved_for_all, from, to, tokenId] + is_authorized jumpi // [from, to, tokenId] + + // check if approved for tokenId + dup3 // [tokenId, from, to, tokenId] + [SINGLE_APPROVAL_LOCATION] // [SINGLE_APPROVAL_LOCATION, tokenId, from, to, tokenId] + LOAD_ELEMENT_FROM_KEYS(0x00) // [address_approved_for_tokenId, from, to, tokenId] + caller eq // [msg.sender_approved_for_tokenId, from, to, tokenId] + is_authorized jumpi // [from, to, tokenId] + jump + is_authorized: + + // update balance of from + 0x01 dup2 // [from, 1, from, to, tokenId] + [BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, 1, from, to, tokenId] + sub dup2 // [from, balance-1, from, to, tokenId] + [BALANCE_LOCATION] + STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] + +} + +#define macro TRANSFER_GIVE_TO() = takes(3) returns (3) { + // retrieve balance + // input stack: // [from, to, tokenId] + dup2 // [to, from, to, tokenId] + [BALANCE_LOCATION] + LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, from, to, tokenId] + 0x01 add // [balance+1, from, to, tokenId] + + // update balance + dup3 // [to, balance+1, from, to, tokenId] + [BALANCE_LOCATION] + STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] + + // update ownerOf + dup2 // [to, from, to, tokenId] + dup4 // [tokenId, to, from, to, tokenId] + [OWNER_LOCATION] + STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] + + // update approval + 0x00 dup4 // [tokenId, address(0), from, to, tokenId] + [SINGLE_APPROVAL_LOCATION] + STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] +} + +#define macro APPROVE() = takes (0) returns (0) { + // get owner + 0x24 calldataload dup1 // [tokenId, tokenId] + [OWNER_LOCATION] + LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, tokenId] + dup1 caller // [msg.sender, owner, owner, tokenId] + eq // [is_sender_owner, owner, tokenId] + + // check if approved for all + caller dup3 // [owner, msg.sender, is_sender_owner, owner, tokenId] + LOAD_ELEMENT_FROM_KEYS(0x00) // [is_approved_for_all, is_sender_owner, owner, tokenId]] + or cont jumpi // [owner, tokenId] + error jump + cont: + + // store approval + 0x04 calldataload dup1 dup4 // [tokenId, spender, spender, owner, tokenId] + [SINGLE_APPROVAL_LOCATION] + STORE_ELEMENT_FROM_KEYS(0x00) // [spender, owner, tokenId] + swap1 // [owner, spender, tokenId] + + // emit the approval event + [APPROVAL_EVENT_SIGNATURE] // [sig, owner, spender, tokenId] + 0x00 0x00 // [0, 0, sig, owner, spender, tokenId] + log4 // [] + + stop + error: + 0x00 0x00 revert +} + +#define macro SET_APPROVAL_FOR_ALL() = takes (0) returns (0) { + 0x24 calldataload // [approved] + 0x04 calldataload // [operator, approved] + caller // [msg.sender, operator, approved] + + STORE_ELEMENT_FROM_KEYS(0x00) // [] + + 0x04 calldataload // [operator] + caller // [msg.sender, operator] + 0x24 calldataload // [approved, msg.sender, operator] + // emit the approval event + 0x00 mstore // [owner, operator] + [APPROVAL_FOR_ALL_EVENT_SIGNATURE] // [sig, owner, operator] + 0x00 0x00 // [0, 32, sig, owner, operator] + log4 // [] + + stop + error: + 0x00 0x00 revert +} + +#define macro TRANSFER_FROM() = takes(0) returns(0) { + // Setup the stack for the transfer function. + 0x44 calldataload // [tokenId] + 0x24 calldataload // [to, tokenId] + 0x04 calldataload // [from, to, tokenId] + + TRANSFER_TAKE_FROM(error) // [from, to, tokenId] + TRANSFER_GIVE_TO() // [from, to, tokenId] + + // Emit the transfer event. + [TRANSFER_EVENT_SIGNATURE] // [sig,from, to, tokenId] + 0x20 0x00 // [0, 0, sig, from, to, tokenId] + log4 // [] + + stop + // Error destination. + error: + 0x00 0x00 revert +} + +#define macro SAFE_TRANSFER_FROM() = takes(0) returns (0) { + 0x00 0x00 revert +} + +#define macro SAFE_TRANSFER_FROM_WITH_DATA() = takes(0) returns (0) { + 0x00 0x00 revert +} + +/* Mint Functions */ +#define macro MINT() = takes(0) returns (0) { + // Ensure that the sender is the owner of the contract. + ONLY_OWNER() + + // Setup the stack for the mint function. + 0x24 calldataload // [tokenId] + 0x04 calldataload // [to, tokenId] + 0x00 // [from (0x00), to, tokenId] + dup3 // [tokenId, from (0x00), to, tokenId] + + //check no one owns it + [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, from (0x00), to, tokenId] + iszero cont jumpi + error jump + + cont: + + // Give tokens to the recipient. + TRANSFER_GIVE_TO() // [from (0x00), to, tokenId] + + // Emit the transfer event. + [TRANSFER_EVENT_SIGNATURE] // [sig, from (0x00), to, tokenId] + 0x00 0x00 // [0, 0, sig, from (0x00), to, tokenId] + log4 // [] + + stop + + error: + 0x00 0x00 revert +} + +// Main Macro +#define macro MAIN() = takes(0) returns (0) { + // Identify which function is being called. + 0x00 calldataload 0xE0 shr + dup1 0xa9059cbb eq transferFrom jumpi + dup1 0x42842e0e eq safeTransferFrom jumpi + dup1 0xb88d4fde eq safeTransferFromWithData jumpi + dup1 0x095ea7b3 eq approve jumpi + dup1 0xa22cb465 eq setApprovalForAll jumpi + dup1 0x081812fc eq getApproved jumpi + dup1 0x40c10f19 eq mint jumpi + dup1 0x70a08231 eq balanceOf jumpi + dup1 0x6352211e eq ownerOf jumpi + dup1 0x06fdde03 eq name jumpi + dup1 0x95d89b41 eq symbol jumpi + dup1 0xc87b56dd eq tokenURI jumpi + dup1 0x01ffc9a7 eq supportsInterface jumpi + dup1 0xe985e9c5 eq isApprovedForAll jumpi + + + + transferFrom: + TRANSFER_FROM() + safeTransferFrom: + SAFE_TRANSFER_FROM() // not implemented yet + safeTransferFromWithData: + SAFE_TRANSFER_FROM_WITH_DATA() // not implemented yet + mint: + MINT() + balanceOf: + BALANCE_OF() + approve: + APPROVE() + getApproved: + GET_APPROVED() + setApprovalForAll: + SET_APPROVAL_FOR_ALL() + name: + NAME() // not implemented yet + symbol: + SYMBOL() // not implemented yet + tokenURI: + TOKEN_URI() // not implemented yet + supportsInterface: + SUPPORTS_INTERFACE() + isApprovedForAll: + IS_APPROVED_FOR_ALL() + ownerOf: + OWNER_OF() +} \ No newline at end of file From 6782921d5f0637f462af3ce2e5ed3cf04b71516b Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Tue, 19 Jul 2022 15:14:00 -0700 Subject: [PATCH 02/27] Remove ownable logic --- src/tokens/ERC721.huff | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 0aba0099..70d9e270 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -1,6 +1,5 @@ /* Imports */ -#include "./contracts/utils/Ownable.huff" #include "./contracts/utils/Address.huff" #include "./contracts/utils/Utils.huff" #include "./contracts/utils/HashMap.huff" @@ -43,8 +42,6 @@ /* Constructor */ #define macro CONSTRUCTOR() = takes(0) returns (0) { - // Set msg.sender as the owner of the contract. - OWNABLE_CONSTRUCTOR() } /// >>>>>>>>>>>>>>>>>>>>> VIEW FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// @@ -272,9 +269,6 @@ /* Mint Functions */ #define macro MINT() = takes(0) returns (0) { - // Ensure that the sender is the owner of the contract. - ONLY_OWNER() - // Setup the stack for the mint function. 0x24 calldataload // [tokenId] 0x04 calldataload // [to, tokenId] From 4a5f23a05c527d85092d75af4fb2a985fec1ae17 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Tue, 19 Jul 2022 15:19:56 -0700 Subject: [PATCH 03/27] Cleanup imports --- src/tokens/ERC721.huff | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 70d9e270..0466c74b 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -1,8 +1,6 @@ /* Imports */ -#include "./contracts/utils/Address.huff" -#include "./contracts/utils/Utils.huff" -#include "./contracts/utils/HashMap.huff" +#include "../data-strcutures/HashMap.huff" /* Interface */ #define function mint(address,uint256) nonpayable returns () From 7813dcec139449b63980c892c3aedc618c8b4441 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Tue, 19 Jul 2022 15:29:07 -0700 Subject: [PATCH 04/27] Make mint internal --- src/tokens/ERC721.huff | 61 +++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 0466c74b..401cc9ac 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -3,8 +3,6 @@ #include "../data-strcutures/HashMap.huff" /* Interface */ -#define function mint(address,uint256) nonpayable returns () - #define function name() nonpayable returns (string) #define function symbol() nonpayable returns (string) #define function tokenURI(uint256) nonpayable returns (string) @@ -116,6 +114,34 @@ 0x20 0x00 return // [] } +/// >>>>>>>>>>>>>>>>>>>>> INTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// + +#define macro _MINT() = takes(2) returns (0) { + // Input stack: // [to, tokenId] + 0x00 // [from (0x00), to, tokenId] + dup3 // [tokenId, from (0x00), to, tokenId] + + //check no one owns it + [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, from (0x00), to, tokenId] + iszero cont jumpi + error jump + + cont: + + // Give tokens to the recipient. + TRANSFER_GIVE_TO() // [from (0x00), to, tokenId] + + // Emit the transfer event. + [TRANSFER_EVENT_SIGNATURE] // [sig, from (0x00), to, tokenId] + 0x00 0x00 // [0, 0, sig, from (0x00), to, tokenId] + log4 // [] + + stop + + error: + 0x00 0x00 revert +} + /// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// /* Transfer Functions */ @@ -265,35 +291,6 @@ 0x00 0x00 revert } -/* Mint Functions */ -#define macro MINT() = takes(0) returns (0) { - // Setup the stack for the mint function. - 0x24 calldataload // [tokenId] - 0x04 calldataload // [to, tokenId] - 0x00 // [from (0x00), to, tokenId] - dup3 // [tokenId, from (0x00), to, tokenId] - - //check no one owns it - [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, from (0x00), to, tokenId] - iszero cont jumpi - error jump - - cont: - - // Give tokens to the recipient. - TRANSFER_GIVE_TO() // [from (0x00), to, tokenId] - - // Emit the transfer event. - [TRANSFER_EVENT_SIGNATURE] // [sig, from (0x00), to, tokenId] - 0x00 0x00 // [0, 0, sig, from (0x00), to, tokenId] - log4 // [] - - stop - - error: - 0x00 0x00 revert -} - // Main Macro #define macro MAIN() = takes(0) returns (0) { // Identify which function is being called. @@ -321,8 +318,6 @@ SAFE_TRANSFER_FROM() // not implemented yet safeTransferFromWithData: SAFE_TRANSFER_FROM_WITH_DATA() // not implemented yet - mint: - MINT() balanceOf: BALANCE_OF() approve: From 85ec88d01cc35dfc8e039df7e9cd5d8f4cbe0dac Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Tue, 19 Jul 2022 15:37:21 -0700 Subject: [PATCH 05/27] Setup test suite --- test/tokens/ERC721.t.sol | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index 39cdee3f..dfd05979 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -1,2 +1,32 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.13; +pragma solidity ^0.8.15; + +import "forge-std/Test.sol"; +import "foundry-huff/HuffDeployer.sol"; + +interface ERC721 { + function name() external returns (string memory); + function symbol() external returns (string memory); + function tokenURI(uint256) external returns (string memory); + + function mint(address,uint256) external; + function transfer(address,uint256) external; + function transferFrom(address,address,uint256) external; + function approve(address,uint256) external; + function setApprovalForAll(address,bool) external; + + function getApproved(uint256) external returns (address); + function isApprovedForAll(address,address) external returns (uint256); + function ownerOf(uint256) external returns (address); + function balanceOf(address) external returns (uint256); + function supportsInterface(bytes4) external returns (bool); +} + +contract ERC721Test is Test { + ERC721 erc721; + + function setUp() public { + HuffConfig config = HuffDeployer.config(); + erc721 = ERC721(config.deploy("tokens/ERC721")); + } +} From 6bb754e1849f13a1adce72420371d852a421ff67 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Tue, 19 Jul 2022 16:05:08 -0700 Subject: [PATCH 06/27] Create mock erc721 --- test/tokens/ERC721.t.sol | 6 +++++- test/tokens/mocks/MockERC721.huff | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 test/tokens/mocks/MockERC721.huff diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index dfd05979..0debd1bd 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -26,7 +26,11 @@ contract ERC721Test is Test { ERC721 erc721; function setUp() public { - HuffConfig config = HuffDeployer.config(); + // Read mock erc721 from file + string memory mockErc721 = vm.readFile("test/tokens/mocks/MockERC721.huff"); + + // Create mock erc721 + HuffConfig config = HuffDeployer.config().with_code(mockErc721); erc721 = ERC721(config.deploy("tokens/ERC721")); } } diff --git a/test/tokens/mocks/MockERC721.huff b/test/tokens/mocks/MockERC721.huff new file mode 100644 index 00000000..ffdbce33 --- /dev/null +++ b/test/tokens/mocks/MockERC721.huff @@ -0,0 +1,20 @@ +#define function mint(address, uint256) payable returns () + +#define macro MINT() = takes(0) returns(0) { + 0x04 calldataload // [tokenId] + 0x04 calldataload // [to, tokenId] + _MINT() +} + +/* Main Macro - The contract entrypoint */ +#define macro MAIN() = takes(0) returns(0) { + // Identify which function is being called using the 4 byte function signature + 0x00 calldataload 0xE0 shr + dup1 0x40c10f19 eq mint jumpi + + // Revert if no matching signature + 0x00 0x00 revert + + mint: + MINT() +} \ No newline at end of file From eda20b24c687ecbe1383d6b25bf522396f5d38a1 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Wed, 20 Jul 2022 11:38:30 -0700 Subject: [PATCH 07/27] Fix compiler errors --- src/tokens/ERC721.huff | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 401cc9ac..0c32d1cd 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -1,6 +1,6 @@ /* Imports */ -#include "../data-strcutures/HashMap.huff" +#include "../data-structures/HashMap.huff" /* Interface */ #define function name() nonpayable returns (string) @@ -301,7 +301,6 @@ dup1 0x095ea7b3 eq approve jumpi dup1 0xa22cb465 eq setApprovalForAll jumpi dup1 0x081812fc eq getApproved jumpi - dup1 0x40c10f19 eq mint jumpi dup1 0x70a08231 eq balanceOf jumpi dup1 0x6352211e eq ownerOf jumpi dup1 0x06fdde03 eq name jumpi From 854941adc1d0c724bdb30c59004b24263ae2f995 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Thu, 21 Jul 2022 15:18:25 -0700 Subject: [PATCH 08/27] Add name macro --- src/tokens/ERC721.huff | 9 ++++----- test/tokens/mocks/MockERC721.huff | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 0c32d1cd..6b9730bd 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -29,19 +29,18 @@ #define constant APPROVAL_FOR_ALL_EVENT_SIGNATURE = 0x17307EAB39AB6107E8899845AD3D59BD9653F200F220920489CA2B5937696C31 /* Storage Slots */ -#define constant NAME_LOCATION = FREE_STORAGE_POINTER() #define constant SYMBOL_LOCATION = FREE_STORAGE_POINTER() #define constant OWNER_LOCATION = FREE_STORAGE_POINTER() // ownerOf #define constant BALANCE_LOCATION = FREE_STORAGE_POINTER() // balanceOf #define constant SINGLE_APPROVAL_LOCATION = FREE_STORAGE_POINTER() // getApproved -/* Constructor */ +/// >>>>>>>>>>>>>>>>>>>>> VIEW FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// -#define macro CONSTRUCTOR() = takes(0) returns (0) { +#define macro NAME() = takes (0) returns (0) { + // Must setup override + NAME_OVERRIDE() } -/// >>>>>>>>>>>>>>>>>>>>> VIEW FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// - #define macro BALANCE_OF() = takes (0) returns (0) { 0x04 calldataload // [account] [BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance] diff --git a/test/tokens/mocks/MockERC721.huff b/test/tokens/mocks/MockERC721.huff index ffdbce33..1e8fec5d 100644 --- a/test/tokens/mocks/MockERC721.huff +++ b/test/tokens/mocks/MockERC721.huff @@ -1,13 +1,27 @@ +// Interface #define function mint(address, uint256) payable returns () -#define macro MINT() = takes(0) returns(0) { +// Constants +#define constant META_NAME = 0x05546f6b656e // "Token" + +/// >>>>>>>>>>>>>>>>>>>>> OVERRIDES <<<<<<<<<<<<<<<<<<<<<< /// + +#define macro NAME_OVERRIDE() = takes (0) returns (0) { + 0x20 0x20 mstore + [META_NAME] 0x45 mstore + 0x60 0x20 return +} + +/// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// + +#define macro MINT() = takes (0) returns (0) { 0x04 calldataload // [tokenId] 0x04 calldataload // [to, tokenId] _MINT() } /* Main Macro - The contract entrypoint */ -#define macro MAIN() = takes(0) returns(0) { +#define macro MAIN() = takes (0) returns (0) { // Identify which function is being called using the 4 byte function signature 0x00 calldataload 0xE0 shr dup1 0x40c10f19 eq mint jumpi From ecef08e8eca0b4fa83c31af72a1fc813ba229732 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Thu, 21 Jul 2022 15:21:31 -0700 Subject: [PATCH 09/27] Add symbol macro --- src/tokens/ERC721.huff | 10 +++++++--- test/tokens/mocks/MockERC721.huff | 7 +++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 6b9730bd..6f1ac695 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -29,7 +29,6 @@ #define constant APPROVAL_FOR_ALL_EVENT_SIGNATURE = 0x17307EAB39AB6107E8899845AD3D59BD9653F200F220920489CA2B5937696C31 /* Storage Slots */ -#define constant SYMBOL_LOCATION = FREE_STORAGE_POINTER() #define constant OWNER_LOCATION = FREE_STORAGE_POINTER() // ownerOf #define constant BALANCE_LOCATION = FREE_STORAGE_POINTER() // balanceOf #define constant SINGLE_APPROVAL_LOCATION = FREE_STORAGE_POINTER() // getApproved @@ -41,6 +40,11 @@ NAME_OVERRIDE() } +#define macro SYMBOL() = takes (0) returns (0) { + // Must setup override + SYMBOL_OVERRIDE() +} + #define macro BALANCE_OF() = takes (0) returns (0) { 0x04 calldataload // [account] [BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance] @@ -325,9 +329,9 @@ setApprovalForAll: SET_APPROVAL_FOR_ALL() name: - NAME() // not implemented yet + NAME() symbol: - SYMBOL() // not implemented yet + SYMBOL() tokenURI: TOKEN_URI() // not implemented yet supportsInterface: diff --git a/test/tokens/mocks/MockERC721.huff b/test/tokens/mocks/MockERC721.huff index 1e8fec5d..63c5cada 100644 --- a/test/tokens/mocks/MockERC721.huff +++ b/test/tokens/mocks/MockERC721.huff @@ -3,6 +3,7 @@ // Constants #define constant META_NAME = 0x05546f6b656e // "Token" +#define constant META_SYMBOL = 0x03544B4E // "TKN" /// >>>>>>>>>>>>>>>>>>>>> OVERRIDES <<<<<<<<<<<<<<<<<<<<<< /// @@ -12,6 +13,12 @@ 0x60 0x20 return } +#define macro SYMBOL_OVERRIDE() = takes (0) returns (0) { + 0x20 0x20 mstore + [META_SYMBOL] 0x43 mstore + 0x60 0x20 return +} + /// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// #define macro MINT() = takes (0) returns (0) { From 0f257070addfc7e309f10e281f1b3ee1bc83d0dc Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Mon, 12 Sep 2022 15:43:50 -0700 Subject: [PATCH 10/27] Reconfigure wrapper logic --- src/tokens/ERC721.huff | 56 -------------------------- test/tokens/ERC721.t.sol | 2 +- test/tokens/mocks/ERC721Wrapper.huff | 59 ++++++++++++++++++++++++++++ test/tokens/mocks/MockERC721.huff | 41 ------------------- 4 files changed, 60 insertions(+), 98 deletions(-) create mode 100644 test/tokens/mocks/ERC721Wrapper.huff delete mode 100644 test/tokens/mocks/MockERC721.huff diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 6f1ac695..96fa2122 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -76,14 +76,6 @@ 0x20 0x00 return } -#define macro NAME() = takes (0) returns (0) { - 0x00 0x00 revert -} - -#define macro SYMBOL() = takes (0) returns (0) { - 0x00 0x00 revert -} - #define macro TOKEN_URI() = takes (0) returns (0) { 0x00 0x00 revert } @@ -292,52 +284,4 @@ #define macro SAFE_TRANSFER_FROM_WITH_DATA() = takes(0) returns (0) { 0x00 0x00 revert -} - -// Main Macro -#define macro MAIN() = takes(0) returns (0) { - // Identify which function is being called. - 0x00 calldataload 0xE0 shr - dup1 0xa9059cbb eq transferFrom jumpi - dup1 0x42842e0e eq safeTransferFrom jumpi - dup1 0xb88d4fde eq safeTransferFromWithData jumpi - dup1 0x095ea7b3 eq approve jumpi - dup1 0xa22cb465 eq setApprovalForAll jumpi - dup1 0x081812fc eq getApproved jumpi - dup1 0x70a08231 eq balanceOf jumpi - dup1 0x6352211e eq ownerOf jumpi - dup1 0x06fdde03 eq name jumpi - dup1 0x95d89b41 eq symbol jumpi - dup1 0xc87b56dd eq tokenURI jumpi - dup1 0x01ffc9a7 eq supportsInterface jumpi - dup1 0xe985e9c5 eq isApprovedForAll jumpi - - - - transferFrom: - TRANSFER_FROM() - safeTransferFrom: - SAFE_TRANSFER_FROM() // not implemented yet - safeTransferFromWithData: - SAFE_TRANSFER_FROM_WITH_DATA() // not implemented yet - balanceOf: - BALANCE_OF() - approve: - APPROVE() - getApproved: - GET_APPROVED() - setApprovalForAll: - SET_APPROVAL_FOR_ALL() - name: - NAME() - symbol: - SYMBOL() - tokenURI: - TOKEN_URI() // not implemented yet - supportsInterface: - SUPPORTS_INTERFACE() - isApprovedForAll: - IS_APPROVED_FOR_ALL() - ownerOf: - OWNER_OF() } \ No newline at end of file diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index 0debd1bd..df5eafca 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -27,7 +27,7 @@ contract ERC721Test is Test { function setUp() public { // Read mock erc721 from file - string memory mockErc721 = vm.readFile("test/tokens/mocks/MockERC721.huff"); + string memory mockErc721 = vm.readFile("test/tokens/mocks/ERC721Wrapper.huff"); // Create mock erc721 HuffConfig config = HuffDeployer.config().with_code(mockErc721); diff --git a/test/tokens/mocks/ERC721Wrapper.huff b/test/tokens/mocks/ERC721Wrapper.huff new file mode 100644 index 00000000..9c90f2b1 --- /dev/null +++ b/test/tokens/mocks/ERC721Wrapper.huff @@ -0,0 +1,59 @@ +// Interface +#define function mint(address, uint256) payable returns () + +/// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// + +#define macro MINT() = takes (0) returns (0) { + 0x04 calldataload // [tokenId] + 0x04 calldataload // [to, tokenId] + _MINT() +} + +/* Main Macro - The contract entrypoint */ +#define macro MAIN() = takes(0) returns (0) { + // Identify which function is being called. + 0x00 calldataload 0xE0 shr + dup1 0x40c10f19 eq mint jumpi + dup1 0xa9059cbb eq transferFrom jumpi + dup1 0x42842e0e eq safeTransferFrom jumpi + dup1 0xb88d4fde eq safeTransferFromWithData jumpi + dup1 0x095ea7b3 eq approve jumpi + dup1 0xa22cb465 eq setApprovalForAll jumpi + dup1 0x081812fc eq getApproved jumpi + dup1 0x70a08231 eq balanceOf jumpi + dup1 0x6352211e eq ownerOf jumpi + dup1 0x06fdde03 eq name jumpi + dup1 0x95d89b41 eq symbol jumpi + dup1 0xc87b56dd eq tokenURI jumpi + dup1 0x01ffc9a7 eq supportsInterface jumpi + dup1 0xe985e9c5 eq isApprovedForAll jumpi + + mint: + MINT() + transferFrom: + TRANSFER_FROM() + safeTransferFrom: + SAFE_TRANSFER_FROM() // not implemented yet + safeTransferFromWithData: + SAFE_TRANSFER_FROM_WITH_DATA() // not implemented yet + balanceOf: + BALANCE_OF() + approve: + APPROVE() + getApproved: + GET_APPROVED() + setApprovalForAll: + SET_APPROVAL_FOR_ALL() + name: + NAME() + symbol: + SYMBOL() + tokenURI: + TOKEN_URI() // not implemented yet + supportsInterface: + SUPPORTS_INTERFACE() + isApprovedForAll: + IS_APPROVED_FOR_ALL() + ownerOf: + OWNER_OF() +} \ No newline at end of file diff --git a/test/tokens/mocks/MockERC721.huff b/test/tokens/mocks/MockERC721.huff deleted file mode 100644 index 63c5cada..00000000 --- a/test/tokens/mocks/MockERC721.huff +++ /dev/null @@ -1,41 +0,0 @@ -// Interface -#define function mint(address, uint256) payable returns () - -// Constants -#define constant META_NAME = 0x05546f6b656e // "Token" -#define constant META_SYMBOL = 0x03544B4E // "TKN" - -/// >>>>>>>>>>>>>>>>>>>>> OVERRIDES <<<<<<<<<<<<<<<<<<<<<< /// - -#define macro NAME_OVERRIDE() = takes (0) returns (0) { - 0x20 0x20 mstore - [META_NAME] 0x45 mstore - 0x60 0x20 return -} - -#define macro SYMBOL_OVERRIDE() = takes (0) returns (0) { - 0x20 0x20 mstore - [META_SYMBOL] 0x43 mstore - 0x60 0x20 return -} - -/// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// - -#define macro MINT() = takes (0) returns (0) { - 0x04 calldataload // [tokenId] - 0x04 calldataload // [to, tokenId] - _MINT() -} - -/* Main Macro - The contract entrypoint */ -#define macro MAIN() = takes (0) returns (0) { - // Identify which function is being called using the 4 byte function signature - 0x00 calldataload 0xE0 shr - dup1 0x40c10f19 eq mint jumpi - - // Revert if no matching signature - 0x00 0x00 revert - - mint: - MINT() -} \ No newline at end of file From f65dfd02a9886333ebb637993881b7737c1a0596 Mon Sep 17 00:00:00 2001 From: kadenzipfel Date: Mon, 12 Sep 2022 15:55:13 -0700 Subject: [PATCH 11/27] Name and symbol macros --- src/tokens/ERC721.huff | 14 ++++++++++---- test/tokens/ERC721.t.sol | 5 +++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 96fa2122..dd69e629 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -36,13 +36,19 @@ /// >>>>>>>>>>>>>>>>>>>>> VIEW FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// #define macro NAME() = takes (0) returns (0) { - // Must setup override - NAME_OVERRIDE() + 0x05546f6b656e // [`${len}Token`] + 0x25 mstore // [] + + 0x20 returndatasize mstore // [] + 0x60 returndatasize return } #define macro SYMBOL() = takes (0) returns (0) { - // Must setup override - SYMBOL_OVERRIDE() + 0x03544B4E // [`${len}TKN`] + 0x23 mstore // [] + + 0x20 returndatasize mstore // [] + 0x60 returndatasize return } #define macro BALANCE_OF() = takes (0) returns (0) { diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index df5eafca..6e8927fc 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -33,4 +33,9 @@ contract ERC721Test is Test { HuffConfig config = HuffDeployer.config().with_code(mockErc721); erc721 = ERC721(config.deploy("tokens/ERC721")); } + + function testMetadata() public { + assertEq(erc721.name(), "Token"); + assertEq(erc721.symbol(), "TKN"); + } } From 1ba6be624b705f4e9d3e24d5da07457567f04c2c Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Sep 2022 15:41:25 -0400 Subject: [PATCH 12/27] error enhancements --- src/tokens/ERC20.huff | 35 ++------- src/tokens/ERC721.huff | 109 +++++++++++++++++---------- src/utils/CommonErrors.huff | 23 ++++++ src/utils/NonPayable.huff | 16 ++++ test/tokens/ERC721.t.sol | 18 ++--- test/tokens/mocks/ERC721Wrapper.huff | 54 ++++++++----- 6 files changed, 158 insertions(+), 97 deletions(-) create mode 100644 src/utils/NonPayable.huff diff --git a/src/tokens/ERC20.huff b/src/tokens/ERC20.huff index 6d7c674f..037ee495 100644 --- a/src/tokens/ERC20.huff +++ b/src/tokens/ERC20.huff @@ -4,10 +4,11 @@ /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. /// @notice Modified from Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol) +// Imports +#include "../utils/Errors.huff" +#include "../utils/NonPayable.huff" #include "../data-structures/Hashmap.huff" -// TODO: Add revert strings (and related tests) - // Interface #define function allowance(address,address) view returns (uint256) #define function approve(address,uint256) nonpayable returns () // these returns sb updated @@ -398,12 +399,14 @@ expired: 0x5045524D49545F444541444C494E455F45585049524544000000000000000000 // ["PERMIT_DEADLINE_EXPIRED"] 0x17 // [23 (length), "PERMIT_DEADLINE_EXPIRED"] - REVERT_WITH_REASON(0x00) + 0x00 // [0, 23 (length), "PERMIT_DEADLINE_EXPIRED"] + REQUIRE() invalidSigner: 0x494E56414C49445F5349474E4552000000000000000000000000000000000000 // ["INVALID_SIGNER"] 0x0e // [14 (length), "INVALID_SIGNER"] - REVERT_WITH_REASON(0x00) + 0x00 // [0, 14 (length), "INVALID_SIGNER"] + REQUIRE() } /// @notice Takes an address off the stack, returns the current nonce for that address onto the stack. @@ -530,30 +533,6 @@ log3 // [] } -/// @notice Reverts if the call has a non-zero value. -#define macro NON_PAYABLE() = takes (0) returns (0) { - callvalue iszero // [msg.value == 0] - novalue jumpi // [] - - // Revert with a "NON_PAYABLE" message - 0xb4e4f4e5f50415941424c4500000000000000000000000000000000000000000 // ["NON_PAYABLE"] - 0x1b // [11 (length), "NON_PAYABLE"] - REVERT_WITH_REASON(0x00) // [] - - novalue: -} - -/// @notice Revert with a string reason -#define macro REVERT_WITH_REASON(mem_ptr) = takes (2) returns(0) { - // NOTE: String must be < 32 bytes - // [len, "str"] - [ERROR_SIG] mstore // [len, "str"] 0x00 error sig - 0x20 0x04 add mstore // [len, "str"] 0x00 error sig 0x04 offset - 0x24 add mstore // ["str"] 0x00 error sig 0x04 offset 0x24 len - 0x44 add mstore // [] 0x00 error sig 0x04 offset 0x24 len 0x44: "string" - 0x64 0x00 revert -} - // Function Dispatching #define macro MAIN_ERC20() = takes (1) returns (1) { diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 436b1c5f..71b92af9 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -1,9 +1,12 @@ -/// @title BitPackLib +/// @title ERC721 +/// @author asnared /// @author kadenzipfel /// @notice Modern and heavily gas golfed ERC-721 implementation /// @notice Adapted from Solmate https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol // Imports +#include "../utils/CommonErrors.huff" +#include "../utils/NonPayable.huff" #include "../data-structures/HashMap.huff" // Interface @@ -23,45 +26,60 @@ #define function balanceOf(address) view returns (uint256) #define function supportsInterface(bytes4) view returns (bool) +// Events #define event Transfer(address,address,uint256) #define event Approval(address,address,uint256) #define event ApprovalForAll(address,address,bool) -/* Events Signatures */ -#define constant TRANSFER_EVENT_SIGNATURE = 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF -#define constant APPROVAL_EVENT_SIGNATURE = 0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925 -#define constant APPROVAL_FOR_ALL_EVENT_SIGNATURE = 0x17307EAB39AB6107E8899845AD3D59BD9653F200F220920489CA2B5937696C31 - -/* Storage Slots */ +// Storage Slots #define constant OWNER_LOCATION = FREE_STORAGE_POINTER() // ownerOf #define constant BALANCE_LOCATION = FREE_STORAGE_POINTER() // balanceOf #define constant SINGLE_APPROVAL_LOCATION = FREE_STORAGE_POINTER() // getApproved +// Metadata +// META_NAME = "Token" +#define constant META_NAME = 0x546f6b656e000000000000000000000000000000000000000000000000000000 +#define constant META_NAME_LENGTH = 0x05 + +// META_SYMBOL = "TKN" +#define constant META_SYMBOL = 0x544B4E0000000000000000000000000000000000000000000000000000000000 +#define constant META_SYMBOL_LENGTH = 0x03 + + /// >>>>>>>>>>>>>>>>>>>>> VIEW FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// +/// @notice Name +/// @notice Returns the token name string #define macro NAME() = takes (0) returns (0) { - 0x05546f6b656e // [`${len}Token`] - 0x25 mstore // [] - - 0x20 returndatasize mstore // [] - 0x60 returndatasize return + NON_PAYABLE() // [] + 0x20 0x00 mstore // [] + [META_NAME_LENGTH] 0x20 mstore // [] + [META_NAME] 0x40 mstore // [] + 0x60 0x00 return // [] } +/// @notice Symbol +/// @notice Returns the symbol of the token #define macro SYMBOL() = takes (0) returns (0) { - 0x03544B4E // [`${len}TKN`] - 0x23 mstore // [] - - 0x20 returndatasize mstore // [] - 0x60 returndatasize return + NON_PAYABLE() // [] + 0x20 0x00 mstore // [] + [META_SYMBOL_LENGTH] 0x20 mstore // [] + [META_SYMBOL] 0x40 mstore // [] + 0x60 0x00 return // [] } +/// @notice Balance Of +/// @notice Returns the balance of the given address #define macro BALANCE_OF() = takes (0) returns (0) { - 0x04 calldataload // [account] - [BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance] - 0x00 mstore // [] - 0x20 0x00 return // [] + NON_PAYABLE() // [] + 0x04 calldataload // [account] + [BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance] + 0x00 mstore // [] + 0x20 0x00 return // [] } +/// @notice Owner Of +/// @notice Returns the owner of the given token id #define macro OWNER_OF() = takes (0) returns (0) { 0x04 calldataload // [tokenId] [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner] @@ -69,23 +87,27 @@ 0x20 0x00 return // [] } +/// @notice Is Approved For All +/// @notice Returns whether the given operator is approved for all tokens of the given owner #define macro IS_APPROVED_FOR_ALL() = takes (0) returns (0) { 0x24 calldataload // [to] 0x04 calldataload // [from, to] LOAD_ELEMENT_FROM_KEYS(0x00) // [value] - - 0x00 mstore - 0x20 0x00 return + 0x00 mstore // [] + 0x20 0x00 return // [] } +/// @notice Get Approved +/// @notice Returns the approved address for the given token id #define macro GET_APPROVED() = takes (0) returns (0) { 0x04 calldataload // [tokenId] - [SINGLE_APPROVAL_LOCATION] + [SINGLE_APPROVAL_LOCATION] // [approval_slot, tokenId] LOAD_ELEMENT_FROM_KEYS(0x00) // [spender] - 0x00 mstore - 0x20 0x00 return + 0x00 mstore // [] + 0x20 0x00 return // [] } + #define macro TOKEN_URI() = takes (0) returns (0) { 0x00 0x00 revert } @@ -93,7 +115,7 @@ #define macro SUPPORTS_INTERFACE() = takes (0) returns (0) { // grab interfaceId 0x04 calldataload // [interfaceId] - 0xe0 + 0xe0 shr // check if erc165 interfaceId @@ -121,30 +143,37 @@ /// >>>>>>>>>>>>>>>>>>>>> INTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// +/// @notice Mint +/// @notice Mints a new token #define macro _MINT() = takes(2) returns (0) { // Input stack: // [to, tokenId] + + // Check that the recipient is valid + dup1 iszero invalid_recipient jumpi // [to, tokenId] + + // Create the minting params 0x00 // [from (0x00), to, tokenId] dup3 // [tokenId, from (0x00), to, tokenId] - //check no one owns it + // Check token ownership [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, from (0x00), to, tokenId] - iszero cont jumpi - error jump - - cont: + iszero iszero unauthorized jumpi // Give tokens to the recipient. TRANSFER_GIVE_TO() // [from (0x00), to, tokenId] // Emit the transfer event. - [TRANSFER_EVENT_SIGNATURE] // [sig, from (0x00), to, tokenId] + __EVENT_HASH(Transfer) // [sig, from (0x00), to, tokenId] 0x00 0x00 // [0, 0, sig, from (0x00), to, tokenId] log4 // [] stop - error: - 0x00 0x00 revert + invalid_recipient: + INVALID_RECIPIENT(0x00) + + unauthorized: + ALREADY_MINTED(0x00) } /// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// @@ -179,7 +208,7 @@ LOAD_ELEMENT_FROM_KEYS(0x00) // [address_approved_for_tokenId, from, to, tokenId] caller eq // [msg.sender_approved_for_tokenId, from, to, tokenId] is_authorized jumpi // [from, to, tokenId] - jump + jump is_authorized: // update balance of from @@ -238,7 +267,7 @@ swap1 // [owner, spender, tokenId] // emit the approval event - [APPROVAL_EVENT_SIGNATURE] // [sig, owner, spender, tokenId] + __EVENT_HASH(Approval) // [sig, owner, spender, tokenId] 0x00 0x00 // [0, 0, sig, owner, spender, tokenId] log4 // [] @@ -259,7 +288,7 @@ 0x24 calldataload // [approved, msg.sender, operator] // emit the approval event 0x00 mstore // [owner, operator] - [APPROVAL_FOR_ALL_EVENT_SIGNATURE] // [sig, owner, operator] + __EVENT_HASH(ApprovalForAll) // [sig, owner, operator] 0x00 0x00 // [0, 32, sig, owner, operator] log4 // [] @@ -278,7 +307,7 @@ TRANSFER_GIVE_TO() // [from, to, tokenId] // Emit the transfer event. - [TRANSFER_EVENT_SIGNATURE] // [sig,from, to, tokenId] + __EVENT_HASH(Transfer) // [sig,from, to, tokenId] 0x20 0x00 // [0, 0, sig, from, to, tokenId] log4 // [] diff --git a/src/utils/CommonErrors.huff b/src/utils/CommonErrors.huff index acdf4871..f77f3ce0 100644 --- a/src/utils/CommonErrors.huff +++ b/src/utils/CommonErrors.huff @@ -21,6 +21,12 @@ #define constant DISPATCH_ERROR_MESSAGE = 0xe44495350415443485f4552524f52000000000000000000000000000000000000 #define constant DISPATCH_LENGTH = 0x0e +#define constant INVALID_RECIPIENT_ERROR = 0x11494e56414c49445f524543495049454e54000000000000000000000000000000 +#define constant INVALID_RECIPIENT_LENGTH = 0x11 + +#define constant ALREADY_MINTED_ERROR = 0xe414c52454144595f4d494e544544000000000000000000000000000000000000 +#define constant ALREADY_MINTED_LENGTH = 0x0e + /// @notice Reverts with an "NOT_AUTHORIZED" message if the condition is false #define macro UNAUTHORIZED(condition) = takes (0) returns (0) { [NOT_AUTHORIZED_ERROR] // ["NOT_AUTHORIZED"] @@ -45,6 +51,23 @@ REQUIRE() // [] } +/// @notice Reverts with an "ALREADY_MINTED" message if condition is false +#define macro ALREADY_MINTED(condition) = takes (0) returns (0) { + [ALREADY_MINTED_ERROR] // ["ALREADY_MINTED"] + [ALREADY_MINTED_LENGTH] // [14 (length), "ALREADY_MINTED"] + // [condition, 14 (length), "ALREADY_MINTED"] + REQUIRE() // [] +} + + +/// @notice Reverts with a "INVALID_RECIPIENT" message if condition is false +#define macro INVALID_RECIPIENT(condition) = takes (0) returns (0) { + [INVALID_RECIPIENT_ERROR] // ["INVALID_RECIPIENT"] + [INVALID_RECIPIENT_LENGTH] // [17 (length), "INVALID_RECIPIENT"] + // [condition, 17 (length), "INVALID_RECIPIENT"] + REQUIRE() // [] +} + /// @notice Reverts with a "REENTRANCY" message if condition is false #define macro REENTRANCY(condition) = takes (0) returns (0) { [REENTRANCY_ERROR] // ["REENTRANCY"] diff --git a/src/utils/NonPayable.huff b/src/utils/NonPayable.huff new file mode 100644 index 00000000..e450e797 --- /dev/null +++ b/src/utils/NonPayable.huff @@ -0,0 +1,16 @@ +/// @title Non Payable +/// @author asnared +/// @notice Simple macro to revert if a call has a value + +// "NON_PAYABLE" Revert Message String +#define constant NON_PAYABLE_ERROR = 0xb4e4f4e5f50415941424c45000000000000000000000000000000000000000000 +#define constant NON_PAYABLE_LENGTH = 0x0b + +/// @notice Reverts if the call has a non-zero value +/// @notice Reverts with message "NON_PAYABLE" +#define macro NON_PAYABLE() = takes (0) returns (0) { + [NON_PAYABLE_ERROR] // ["NON_PAYABLE"] + [NON_PAYABLE_LENGTH] // [11 (length), "NON_PAYABLE"] + callvalue iszero // [msg.value == 0, 11 (length), "NON_PAYABLE"] + REQUIRE() // [] +} \ No newline at end of file diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index 6e8927fc..be079b04 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.15; import "forge-std/Test.sol"; import "foundry-huff/HuffDeployer.sol"; -interface ERC721 { +interface IERC721 { function name() external returns (string memory); function symbol() external returns (string memory); function tokenURI(uint256) external returns (string memory); @@ -23,19 +23,17 @@ interface ERC721 { } contract ERC721Test is Test { - ERC721 erc721; + IERC721 erc721; function setUp() public { - // Read mock erc721 from file - string memory mockErc721 = vm.readFile("test/tokens/mocks/ERC721Wrapper.huff"); - - // Create mock erc721 - HuffConfig config = HuffDeployer.config().with_code(mockErc721); - erc721 = ERC721(config.deploy("tokens/ERC721")); + // Deploy the ERC721 + string memory wrapper_code = vm.readFile("test/tokens/mocks/ERC721Wrapper.huff"); + erc721 = IERC721(HuffDeployer.config().with_code(wrapper_code).deploy("tokens/ERC721")); } + /// @notice Test the ERC721 Metadata function testMetadata() public { - assertEq(erc721.name(), "Token"); - assertEq(erc721.symbol(), "TKN"); + assertEq(keccak256(abi.encode(erc721.name())), keccak256(abi.encode("Token"))); + assertEq(keccak256(abi.encode(erc721.symbol())), keccak256(abi.encode("TKN"))); } } diff --git a/test/tokens/mocks/ERC721Wrapper.huff b/test/tokens/mocks/ERC721Wrapper.huff index 9c90f2b1..368736a2 100644 --- a/test/tokens/mocks/ERC721Wrapper.huff +++ b/test/tokens/mocks/ERC721Wrapper.huff @@ -1,4 +1,20 @@ // Interface +#define function name() nonpayable returns (string) +#define function symbol() nonpayable returns (string) +#define function tokenURI(uint256) nonpayable returns (string) + +#define function mint(address,uint256) nonpayable returns () +#define function transfer(address,uint256) nonpayable returns () +#define function transferFrom(address,address,uint256) nonpayable returns () +#define function approve(address,uint256) nonpayable returns () +#define function setApprovalForAll(address,bool) nonpayable returns () + +#define function getApproved(uint256) view returns (address) +#define function isApprovedForAll(address,address) view returns (uint256) +#define function ownerOf(uint256) view returns (address) +#define function balanceOf(address) view returns (uint256) +#define function supportsInterface(bytes4) view returns (bool) + #define function mint(address, uint256) payable returns () /// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// @@ -9,26 +25,26 @@ _MINT() } -/* Main Macro - The contract entrypoint */ +// Function Dispatch #define macro MAIN() = takes(0) returns (0) { - // Identify which function is being called. - 0x00 calldataload 0xE0 shr - dup1 0x40c10f19 eq mint jumpi - dup1 0xa9059cbb eq transferFrom jumpi - dup1 0x42842e0e eq safeTransferFrom jumpi - dup1 0xb88d4fde eq safeTransferFromWithData jumpi - dup1 0x095ea7b3 eq approve jumpi - dup1 0xa22cb465 eq setApprovalForAll jumpi - dup1 0x081812fc eq getApproved jumpi - dup1 0x70a08231 eq balanceOf jumpi - dup1 0x6352211e eq ownerOf jumpi - dup1 0x06fdde03 eq name jumpi - dup1 0x95d89b41 eq symbol jumpi - dup1 0xc87b56dd eq tokenURI jumpi - dup1 0x01ffc9a7 eq supportsInterface jumpi - dup1 0xe985e9c5 eq isApprovedForAll jumpi - - mint: + 0x00 calldataload 0xE0 shr // [sig] + + dup1 __FUNC_SIG(mint) eq mint_jump jumpi + dup1 __FUNC_SIG(transferFrom) eq transferFrom jumpi + dup1 __FUNC_SIG(safeTransferFrom) eq safeTransferFrom jumpi + dup1 __FUNC_SIG(safeTransferFromWithData) eq safeTransferFromWithData jumpi + dup1 __FUNC_SIG(approve) eq approve jumpi + dup1 __FUNC_SIG(setApprovalForAll) eq setApprovalForAll jumpi + dup1 __FUNC_SIG(getApproved) eq getApproved jumpi + dup1 __FUNC_SIG(balanceOf) eq balanceOf jumpi + dup1 __FUNC_SIG(ownerOf) eq ownerOf jumpi + dup1 __FUNC_SIG(name) eq name jumpi + dup1 __FUNC_SIG(symbol) eq symbol jumpi + dup1 __FUNC_SIG(tokenURI) eq tokenURI jumpi + dup1 __FUNC_SIG(supportsInterface) eq supportsInterface jumpi + dup1 __FUNC_SIG(isApprovedForAll) eq isApprovedForAll jumpi + + mint_jump: MINT() transferFrom: TRANSFER_FROM() From 492af6541597516e25db7f4b260c55abd60a2ab3 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Sep 2022 16:27:07 -0400 Subject: [PATCH 13/27] fixes --- lib/forge-std | 2 +- src/data-structures/Arrays.huff | 5 ++++ src/data-structures/Hashmap.huff | 2 +- src/tokens/ERC721.huff | 2 +- src/utils/CommonErrors.huff | 12 ++++----- test/data-structures/Bytes.t.sol | 3 +++ test/lib/HuffTest.sol | 20 --------------- test/tokens/ERC721.t.sol | 38 ++++++++++++++++++++-------- test/tokens/mocks/ERC721Wrapper.huff | 2 +- test/utils/ReentrancyGuard.t.sol | 4 +-- 10 files changed, 47 insertions(+), 43 deletions(-) delete mode 100644 test/lib/HuffTest.sol diff --git a/lib/forge-std b/lib/forge-std index 82e6f537..1556165a 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 82e6f5376c15341629ca23098e0b32d303f44f02 +Subproject commit 1556165a8f3ecfa5620152f77d3a6e5de30d0ff8 diff --git a/src/data-structures/Arrays.huff b/src/data-structures/Arrays.huff index a0fa366e..b1671230 100644 --- a/src/data-structures/Arrays.huff +++ b/src/data-structures/Arrays.huff @@ -1,3 +1,8 @@ +/// @title Arrays +/// @author exp-table +/// @notice Array utility library for Solidity contracts +/// @notice Adapted from + // Sets an array in storage from calldata. // Note that since no assumptions is made regarding the context in which // this function is called, the position of the encoded array in the calldata diff --git a/src/data-structures/Hashmap.huff b/src/data-structures/Hashmap.huff index 95ba6483..7eb105f4 100644 --- a/src/data-structures/Hashmap.huff +++ b/src/data-structures/Hashmap.huff @@ -1,5 +1,5 @@ /// @title HashMap -/// @author Andreas Bigger +/// @author asnared /// @notice A Minimal HashMap based off // Given a piece of data (ie an address), hash it, generating the storage slot for a hashmap. diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 71b92af9..eb7bf905 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -7,7 +7,7 @@ // Imports #include "../utils/CommonErrors.huff" #include "../utils/NonPayable.huff" -#include "../data-structures/HashMap.huff" +#include "../data-structures/Hashmap.huff" // Interface #define function name() nonpayable returns (string) diff --git a/src/utils/CommonErrors.huff b/src/utils/CommonErrors.huff index f77f3ce0..e0ad36e1 100644 --- a/src/utils/CommonErrors.huff +++ b/src/utils/CommonErrors.huff @@ -6,25 +6,25 @@ #include "./Errors.huff" // Error Constants -#define constant NOT_AUTHORIZED_ERROR = 0xe4e4f545f415554484f52495a4544000000000000000000000000000000000000 +#define constant NOT_AUTHORIZED_ERROR = 0x4e4f545f415554484f52495a4544000000000000000000000000000000000000 #define constant NOT_AUTHORIZED_LENGTH = 0x0e -#define constant LENGTH_MISMATCH_ERROR = 0xf4c454e4754485f4d49534d415443480000000000000000000000000000000000 +#define constant LENGTH_MISMATCH_ERROR = 0x4c454e4754485f4d49534d415443480000000000000000000000000000000000 #define constant LENGTH_MISMATCH_LENGTH = 0x0f #define constant UNSAFE_RECIPIENT_ERROR = 0x554e534146455f524543495049454e5400000000000000000000000000000000 #define constant UNSAFE_RECIPIENT_LENGTH = 0x10 -#define constant REENTRANCY_ERROR = 0xa5245454e5452414e435900000000000000000000000000000000000000000000 +#define constant REENTRANCY_ERROR = 0x5245454e5452414e435900000000000000000000000000000000000000000000 #define constant REENTRANCY_LENGTH = 0x0a -#define constant DISPATCH_ERROR_MESSAGE = 0xe44495350415443485f4552524f52000000000000000000000000000000000000 +#define constant DISPATCH_ERROR_MESSAGE = 0x44495350415443485f4552524f52000000000000000000000000000000000000 #define constant DISPATCH_LENGTH = 0x0e -#define constant INVALID_RECIPIENT_ERROR = 0x11494e56414c49445f524543495049454e54000000000000000000000000000000 +#define constant INVALID_RECIPIENT_ERROR = 0x494e56414c49445f524543495049454e54000000000000000000000000000000 #define constant INVALID_RECIPIENT_LENGTH = 0x11 -#define constant ALREADY_MINTED_ERROR = 0xe414c52454144595f4d494e544544000000000000000000000000000000000000 +#define constant ALREADY_MINTED_ERROR = 0x414c52454144595f4d494e544544000000000000000000000000000000000000 #define constant ALREADY_MINTED_LENGTH = 0x0e /// @notice Reverts with an "NOT_AUTHORIZED" message if the condition is false diff --git a/test/data-structures/Bytes.t.sol b/test/data-structures/Bytes.t.sol index e69de29b..03c5472d 100644 --- a/test/data-structures/Bytes.t.sol +++ b/test/data-structures/Bytes.t.sol @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + diff --git a/test/lib/HuffTest.sol b/test/lib/HuffTest.sol deleted file mode 100644 index 8c6318c3..00000000 --- a/test/lib/HuffTest.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.15; - -import "forge-std/Test.sol"; -import "foundry-huff/HuffDeployer.sol"; - -contract HuffTest is Test { - /// @dev The name of the contract. - string internal name; - - /// @dev Create a new HuffTest contract. - constructor(string memory _name) { - name = _name; - } - - /// @dev Deploy a new contract. - function deploy() internal returns (address) { - return HuffDeployer.deploy(name); - } -} \ No newline at end of file diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index be079b04..18f495c1 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -9,31 +9,47 @@ interface IERC721 { function symbol() external returns (string memory); function tokenURI(uint256) external returns (string memory); - function mint(address,uint256) external; - function transfer(address,uint256) external; - function transferFrom(address,address,uint256) external; - function approve(address,uint256) external; - function setApprovalForAll(address,bool) external; + function mint(address, uint256) external; + + function transfer(address, uint256) external; + function transferFrom(address, address, uint256) external; + function approve(address, uint256) external; + function setApprovalForAll(address, bool) external; function getApproved(uint256) external returns (address); - function isApprovedForAll(address,address) external returns (uint256); + function isApprovedForAll(address, address) external returns (uint256); function ownerOf(uint256) external returns (address); function balanceOf(address) external returns (uint256); function supportsInterface(bytes4) external returns (bool); } contract ERC721Test is Test { - IERC721 erc721; + IERC721 token; function setUp() public { // Deploy the ERC721 string memory wrapper_code = vm.readFile("test/tokens/mocks/ERC721Wrapper.huff"); - erc721 = IERC721(HuffDeployer.config().with_code(wrapper_code).deploy("tokens/ERC721")); + token = IERC721(HuffDeployer.config().with_code(wrapper_code).deploy("tokens/ERC721")); } /// @notice Test the ERC721 Metadata - function testMetadata() public { - assertEq(keccak256(abi.encode(erc721.name())), keccak256(abi.encode("Token"))); - assertEq(keccak256(abi.encode(erc721.symbol())), keccak256(abi.encode("TKN"))); + function invariantMetadata() public { + assertEq(keccak256(abi.encode(token.name())), keccak256(abi.encode("Token"))); + assertEq(keccak256(abi.encode(token.symbol())), keccak256(abi.encode("TKN"))); + } + + function testMint() public { + vm.expectRevert(bytes("INVALID_RECIPIENT")); + token.mint(address(0x0), 1337); + + // We can still mint the token + token.mint(address(0xBEEF), 1337); + + assertEq(token.balanceOf(address(0xBEEF)), 1); + assertEq(token.ownerOf(1337), address(0xBEEF)); + + // Minting the same token twice should fail + vm.expectRevert(bytes("ALREADY_MINTED")); + token.mint(address(0xBEEF), 1337); } } diff --git a/test/tokens/mocks/ERC721Wrapper.huff b/test/tokens/mocks/ERC721Wrapper.huff index 368736a2..47bb3ff5 100644 --- a/test/tokens/mocks/ERC721Wrapper.huff +++ b/test/tokens/mocks/ERC721Wrapper.huff @@ -20,7 +20,7 @@ /// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// #define macro MINT() = takes (0) returns (0) { - 0x04 calldataload // [tokenId] + 0x24 calldataload // [tokenId] 0x04 calldataload // [to, tokenId] _MINT() } diff --git a/test/utils/ReentrancyGuard.t.sol b/test/utils/ReentrancyGuard.t.sol index 67aa9c71..ce7f1f77 100644 --- a/test/utils/ReentrancyGuard.t.sol +++ b/test/utils/ReentrancyGuard.t.sol @@ -45,8 +45,8 @@ contract ReentranctGuardTest is Test { state = guard.state(); assertEq(state, 2); - // vm.expectRevert(bytes4("")); - // guard.lock(); + vm.expectRevert(bytes("REENTRANCY")); + guard.lock(); } } From d3de76886a0434d4b8aea3a33717213c7745f960 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Sep 2022 17:48:53 -0400 Subject: [PATCH 14/27] cleaning and fixing --- lib/foundry-huff | 2 +- src/tokens/ERC721.huff | 78 ++++++++++++++++++++++------ src/utils/CommonErrors.huff | 10 ++++ test/tokens/ERC721.t.sol | 59 ++++++++++++++++++++- test/tokens/mocks/ERC721Wrapper.huff | 29 ++++++++--- 5 files changed, 154 insertions(+), 24 deletions(-) diff --git a/lib/foundry-huff b/lib/foundry-huff index c22e6f72..eb9ea316 160000 --- a/lib/foundry-huff +++ b/lib/foundry-huff @@ -1 +1 @@ -Subproject commit c22e6f72a1af4e0585228da9b317bd8e7a6e9b49 +Subproject commit eb9ea316389e8bd580f92908b8454399401d0316 diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index eb7bf905..77fc6eac 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -14,14 +14,16 @@ #define function symbol() nonpayable returns (string) #define function tokenURI(uint256) nonpayable returns (string) -#define function mint(address,uint256) nonpayable returns () +#define function mint(address, uint256) payable returns () +#define function burn(uint256) nonpayable returns () + #define function transfer(address,uint256) nonpayable returns () #define function transferFrom(address,address,uint256) nonpayable returns () #define function approve(address,uint256) nonpayable returns () #define function setApprovalForAll(address,bool) nonpayable returns () #define function getApproved(uint256) view returns (address) -#define function isApprovedForAll(address,address) view returns (uint256) +#define function isApprovedForAll(address,address) view returns (bool) #define function ownerOf(uint256) view returns (address) #define function balanceOf(address) view returns (uint256) #define function supportsInterface(bytes4) view returns (bool) @@ -145,7 +147,8 @@ /// @notice Mint /// @notice Mints a new token -#define macro _MINT() = takes(2) returns (0) { +/// @dev The Mint function is payable +#define macro _MINT() = takes (2) returns (0) { // Input stack: // [to, tokenId] // Check that the recipient is valid @@ -176,9 +179,51 @@ ALREADY_MINTED(0x00) } +/// @notice Burn +/// @notice Burns the token with the given id +#define macro _BURN() = takes (1) returns (0) { + // Input stack: // [tokenId] + NON_PAYABLE() // [tokenId] + + dup1 // [tokenId, tokenId] + [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, tokenId] + + // Check that the recipient is valid + dup1 iszero // [owner == 0, owner, tokenId] + not_minted jumpi // [owner, tokenId] + + // Create the burning params + 0x00 swap1 // [owner, to (0x00), tokenId] + + // Reduce the balance of owner by 1 + 0x01 dup2 // [owner, 1, owner, to, tokenId] + [BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, 1, owner, to, tokenId] + sub dup2 // [owner, balance-1, owner, to, tokenId] + [BALANCE_LOCATION] + STORE_ELEMENT_FROM_KEYS(0x00) // [owner, to, tokenId] + + // Set the owner of the token to 0x00 + 0x00 dup4 [OWNER_LOCATION] // [slot, owner, 0x00, owner, to, tokenId] + STORE_ELEMENT_FROM_KEYS(0x00) // [owner, to, tokenId] + + // Set the approval of the token to 0x00 for the owner + 0x00 dup4 [SINGLE_APPROVAL_LOCATION] // [slot, owner, 0x00, owner, to, tokenId] + STORE_ELEMENT_FROM_KEYS(0x00) // [owner, to, tokenId] + + // Emit the transfer event. + __EVENT_HASH(Transfer) // [sig, owner, to (0x00), tokenId] + 0x00 0x00 // [0, 0, sig, owner, to (0x00), tokenId] + log4 // [] + + stop + + not_minted: + NOT_MINTED(0x00) +} + /// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// -/* Transfer Functions */ +/// @notice Transfer From #define macro TRANSFER_TAKE_FROM(error) = takes(3) returns (3) { // input stack [from, to, tokenId] @@ -245,6 +290,8 @@ STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] } +/// @notice Approve +/// @notice Approves a spender for a specific token #define macro APPROVE() = takes (0) returns (0) { // get owner 0x24 calldataload dup1 // [tokenId, tokenId] @@ -257,7 +304,7 @@ caller dup3 // [owner, msg.sender, is_sender_owner, owner, tokenId] LOAD_ELEMENT_FROM_KEYS(0x00) // [is_approved_for_all, is_sender_owner, owner, tokenId]] or cont jumpi // [owner, tokenId] - error jump + not_authorized jump cont: // store approval @@ -272,29 +319,30 @@ log4 // [] stop - error: - 0x00 0x00 revert + + not_authorized: + UNAUTHORIZED(0x00) } +/// @notice Set Approval For All +/// @notice Sets an operator as approved for all tokens of the caller #define macro SET_APPROVAL_FOR_ALL() = takes (0) returns (0) { + // Store the operator as approved for all 0x24 calldataload // [approved] 0x04 calldataload // [operator, approved] caller // [msg.sender, operator, approved] - STORE_ELEMENT_FROM_KEYS(0x00) // [] - 0x04 calldataload // [operator] - caller // [msg.sender, operator] - 0x24 calldataload // [approved, msg.sender, operator] - // emit the approval event - 0x00 mstore // [owner, operator] + // Emit the ApprovalForAll event + 0x24 calldataload // [approved] + 0x04 calldataload // [operator, approved] + caller // [msg.sender, operator, approved] __EVENT_HASH(ApprovalForAll) // [sig, owner, operator] 0x00 0x00 // [0, 32, sig, owner, operator] log4 // [] + // Stop execution stop - error: - 0x00 0x00 revert } #define macro TRANSFER_FROM() = takes(0) returns(0) { diff --git a/src/utils/CommonErrors.huff b/src/utils/CommonErrors.huff index e0ad36e1..1668e408 100644 --- a/src/utils/CommonErrors.huff +++ b/src/utils/CommonErrors.huff @@ -27,6 +27,9 @@ #define constant ALREADY_MINTED_ERROR = 0x414c52454144595f4d494e544544000000000000000000000000000000000000 #define constant ALREADY_MINTED_LENGTH = 0x0e +#define constant NOT_MINTED_ERROR = 0x4e4f545f4d494e54454400000000000000000000000000000000000000000000 +#define constant NOT_MINTED_LENGTH = 0x0a + /// @notice Reverts with an "NOT_AUTHORIZED" message if the condition is false #define macro UNAUTHORIZED(condition) = takes (0) returns (0) { [NOT_AUTHORIZED_ERROR] // ["NOT_AUTHORIZED"] @@ -59,6 +62,13 @@ REQUIRE() // [] } +/// @notice Reverts with an "NOT_MINTED" message if condition is false +#define macro NOT_MINTED(condition) = takes (0) returns (0) { + [NOT_MINTED_ERROR] // ["NOT_MINTED"] + [NOT_MINTED_LENGTH] // [10 (length), "NOT_MINTED"] + // [condition, 10 (length), "NOT_MINTED"] + REQUIRE() // [] +} /// @notice Reverts with a "INVALID_RECIPIENT" message if condition is false #define macro INVALID_RECIPIENT(condition) = takes (0) returns (0) { diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index 18f495c1..1c4b32ba 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -9,7 +9,8 @@ interface IERC721 { function symbol() external returns (string memory); function tokenURI(uint256) external returns (string memory); - function mint(address, uint256) external; + function mint(address, uint256) payable external; + function burn(uint256) external; function transfer(address, uint256) external; function transferFrom(address, address, uint256) external; @@ -17,7 +18,7 @@ interface IERC721 { function setApprovalForAll(address, bool) external; function getApproved(uint256) external returns (address); - function isApprovedForAll(address, address) external returns (uint256); + function isApprovedForAll(address, address) external returns (bool); function ownerOf(uint256) external returns (address); function balanceOf(address) external returns (uint256); function supportsInterface(bytes4) external returns (bool); @@ -52,4 +53,58 @@ contract ERC721Test is Test { vm.expectRevert(bytes("ALREADY_MINTED")); token.mint(address(0xBEEF), 1337); } + + function testBurn() public { + // We can't burn a token we don't have + assertEq(token.balanceOf(address(this)), 0); + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(0)); + vm.expectRevert(bytes("NOT_MINTED")); + token.burn(1337); + + // Mint a token + token.mint(address(this), 1337); + assertEq(token.balanceOf(address(this)), 1); + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(this)); + + // Set approval and then check that it is zeroed out + token.approve(address(0xBEEF), 1337); + assertEq(token.getApproved(1337), address(0xBEEF)); + + // Now we should be able to burn the minted token + token.burn(1337); + assertEq(token.balanceOf(address(this)), 0); + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(0)); + + vm.expectRevert(bytes("NOT_MINTED")); + token.burn(1337); + } + + function testApprove() public { + token.mint(address(this), 1337); + token.approve(address(0xBEEF), 1337); + assertEq(token.getApproved(1337), address(0xBEEF)); + + token.approve(address(0xCAFE), 1337); + assertEq(token.getApproved(1337), address(0xCAFE)); + } + + function testApproveBurn() public { + token.mint(address(this), 1337); + token.approve(address(0xBEEF), 1337); + token.burn(1337); + + assertEq(token.balanceOf(address(this)), 0); + assertEq(token.getApproved(1337), address(0)); + } + + function testApproveAll() public { + assertFalse(token.isApprovedForAll(address(this), address(0xBEEF))); + token.setApprovalForAll(address(0xBEEF), true); + assertTrue(token.isApprovedForAll(address(this), address(0xBEEF))); + token.setApprovalForAll(address(0xBEEF), false); + assertFalse(token.isApprovedForAll(address(this), address(0xBEEF))); + } } diff --git a/test/tokens/mocks/ERC721Wrapper.huff b/test/tokens/mocks/ERC721Wrapper.huff index 47bb3ff5..1733ebdc 100644 --- a/test/tokens/mocks/ERC721Wrapper.huff +++ b/test/tokens/mocks/ERC721Wrapper.huff @@ -10,12 +10,13 @@ #define function setApprovalForAll(address,bool) nonpayable returns () #define function getApproved(uint256) view returns (address) -#define function isApprovedForAll(address,address) view returns (uint256) +#define function isApprovedForAll(address,address) view returns (bool) #define function ownerOf(uint256) view returns (address) #define function balanceOf(address) view returns (uint256) #define function supportsInterface(bytes4) view returns (bool) #define function mint(address, uint256) payable returns () +#define function burn(uint256) nonpayable returns () /// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// @@ -25,16 +26,25 @@ _MINT() } +#define macro BURN() = takes (0) returns (0) { + 0x04 calldataload // [tokenId] + _BURN() +} + // Function Dispatch #define macro MAIN() = takes(0) returns (0) { 0x00 calldataload 0xE0 shr // [sig] + // Mint and Burning Functions dup1 __FUNC_SIG(mint) eq mint_jump jumpi + dup1 __FUNC_SIG(burn) eq burn_jump jumpi + + dup1 __FUNC_SIG(approve) eq approve jumpi dup1 __FUNC_SIG(transferFrom) eq transferFrom jumpi dup1 __FUNC_SIG(safeTransferFrom) eq safeTransferFrom jumpi dup1 __FUNC_SIG(safeTransferFromWithData) eq safeTransferFromWithData jumpi - dup1 __FUNC_SIG(approve) eq approve jumpi dup1 __FUNC_SIG(setApprovalForAll) eq setApprovalForAll jumpi + dup1 __FUNC_SIG(getApproved) eq getApproved jumpi dup1 __FUNC_SIG(balanceOf) eq balanceOf jumpi dup1 __FUNC_SIG(ownerOf) eq ownerOf jumpi @@ -44,26 +54,33 @@ dup1 __FUNC_SIG(supportsInterface) eq supportsInterface jumpi dup1 __FUNC_SIG(isApprovedForAll) eq isApprovedForAll jumpi + // Revert on failed dispatch + 0x00 dup1 revert + mint_jump: MINT() + burn_jump: + BURN() + transferFrom: TRANSFER_FROM() safeTransferFrom: SAFE_TRANSFER_FROM() // not implemented yet safeTransferFromWithData: SAFE_TRANSFER_FROM_WITH_DATA() // not implemented yet - balanceOf: - BALANCE_OF() approve: APPROVE() - getApproved: - GET_APPROVED() setApprovalForAll: SET_APPROVAL_FOR_ALL() + + getApproved: + GET_APPROVED() name: NAME() symbol: SYMBOL() + balanceOf: + BALANCE_OF() tokenURI: TOKEN_URI() // not implemented yet supportsInterface: From 05fcb2da1081545bebe05e569f6a727e33ef2da1 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Sep 2022 18:31:01 -0400 Subject: [PATCH 15/27] transfer from --- src/tokens/ERC721.huff | 68 +++++++++++++++++---------------- src/utils/CommonErrors.huff | 11 ++++++ test/tokens/ERC721.t.sol | 75 +++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 33 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 77fc6eac..a51340c8 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -224,45 +224,47 @@ /// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// /// @notice Transfer From -#define macro TRANSFER_TAKE_FROM(error) = takes(3) returns (3) { - // input stack [from, to, tokenId] +#define macro TRANSFER_TAKE_FROM() = takes(3) returns (3) { + // Input stack: [from, to, tokenId] - // check if from is the owner of the token - dup1 // [from, from, to, tokenId] - dup4 // [tokenId, from, from, to, tokenId] + // If from !== ownerOf[tokenId] revert with "WRONG_FROM" + dup1 dup4 // [tokenId, from, from, to, tokenId] [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, from, from, to, tokenId] - swap2 // [from, owner, from, to, tokenId] - eq // [from_is_owner, from, to, tokenId] - cont jumpi // [from, to, tokenId] - jump + eq cont jumpi // [from, to, tokenId] + WRONG_FROM(0x00) cont: - // check if msg.sender == from - dup1 caller // [msg.sender, from, from, to, tokenId] - eq // [from_is_msg.sender, from, to, tokenId] + // If to === address(0) revert with "INVALID_RECIPIENT" + dup2 iszero iszero continue jumpi // [from, to, tokenId] + INVALID_RECIPIENT(0x00) + continue: + + // Check if msg.sender == from + dup1 caller eq // [msg.sender == from, from, to, tokenId] is_authorized jumpi // [from, to, tokenId] - // check if approved for all + // Check if approved for all caller dup2 // [from, msg.sender, from, to, tokenId] LOAD_ELEMENT_FROM_KEYS(0x00) // [is_approved_for_all, from, to, tokenId] is_authorized jumpi // [from, to, tokenId] - // check if approved for tokenId + // Check if approved for tokenId dup3 // [tokenId, from, to, tokenId] [SINGLE_APPROVAL_LOCATION] // [SINGLE_APPROVAL_LOCATION, tokenId, from, to, tokenId] LOAD_ELEMENT_FROM_KEYS(0x00) // [address_approved_for_tokenId, from, to, tokenId] - caller eq // [msg.sender_approved_for_tokenId, from, to, tokenId] - is_authorized jumpi // [from, to, tokenId] - jump + caller eq is_authorized jumpi // [from, to, tokenId] + + // If msg.sender != from && !isApprovedForAll[from][msg.sender] && msg.sender != getApproved[id], + UNAUTHORIZED(0x00) + is_authorized: - // update balance of from + // Update balance of from 0x01 dup2 // [from, 1, from, to, tokenId] [BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, 1, from, to, tokenId] sub dup2 // [from, balance-1, from, to, tokenId] [BALANCE_LOCATION] STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] - } #define macro TRANSFER_GIVE_TO() = takes(3) returns (3) { @@ -345,30 +347,30 @@ stop } -#define macro TRANSFER_FROM() = takes(0) returns(0) { +/// @notice Transfer From +/// @notice Transfers a token from one address to another +#define macro TRANSFER_FROM() = takes (0) returns (0) { // Setup the stack for the transfer function. - 0x44 calldataload // [tokenId] - 0x24 calldataload // [to, tokenId] - 0x04 calldataload // [from, to, tokenId] + 0x44 calldataload // [tokenId] + 0x24 calldataload // [to, tokenId] + 0x04 calldataload // [from, to, tokenId] - TRANSFER_TAKE_FROM(error) // [from, to, tokenId] - TRANSFER_GIVE_TO() // [from, to, tokenId] + TRANSFER_TAKE_FROM() // [from, to, tokenId] + TRANSFER_GIVE_TO() // [from, to, tokenId] // Emit the transfer event. - __EVENT_HASH(Transfer) // [sig,from, to, tokenId] - 0x20 0x00 // [0, 0, sig, from, to, tokenId] - log4 // [] + __EVENT_HASH(Transfer) // [sig,from, to, tokenId] + 0x20 0x00 // [0, 0, sig, from, to, tokenId] + log4 // [] + // Stop execution stop - // Error destination. - error: - 0x00 0x00 revert } -#define macro SAFE_TRANSFER_FROM() = takes(0) returns (0) { +#define macro SAFE_TRANSFER_FROM() = takes (0) returns (0) { 0x00 0x00 revert } -#define macro SAFE_TRANSFER_FROM_WITH_DATA() = takes(0) returns (0) { +#define macro SAFE_TRANSFER_FROM_WITH_DATA() = takes (0) returns (0) { 0x00 0x00 revert } \ No newline at end of file diff --git a/src/utils/CommonErrors.huff b/src/utils/CommonErrors.huff index 1668e408..ce4ceced 100644 --- a/src/utils/CommonErrors.huff +++ b/src/utils/CommonErrors.huff @@ -9,6 +9,9 @@ #define constant NOT_AUTHORIZED_ERROR = 0x4e4f545f415554484f52495a4544000000000000000000000000000000000000 #define constant NOT_AUTHORIZED_LENGTH = 0x0e +#define constant WRONG_FROM_ERROR = 0x57524f4e475f46524f4d00000000000000000000000000000000000000000000 +#define constant WRONG_FROM_LENGTH = 0x0a + #define constant LENGTH_MISMATCH_ERROR = 0x4c454e4754485f4d49534d415443480000000000000000000000000000000000 #define constant LENGTH_MISMATCH_LENGTH = 0x0f @@ -38,6 +41,14 @@ REQUIRE() // [] } +/// @notice Reverts with an "WRONG_FROM" message if the condition is false +#define macro WRONG_FROM(condition) = takes (0) returns (0) { + [WRONG_FROM_ERROR] // ["WRONG_FROM"] + [WRONG_FROM_LENGTH] // [10 (length), "WRONG_FROM"] + // [condition, 10 (length), "WRONG_FROM"] + REQUIRE() // [] +} + /// @notice Reverts with an "LENGTH_MISMATCH" message if condition is false #define macro LENGTH_MISMATCH(condition) = takes (0) returns (0) { [LENGTH_MISMATCH_ERROR] // ["LENGTH_MISMATCH"] diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index 1c4b32ba..11c54d8e 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -107,4 +107,79 @@ contract ERC721Test is Test { token.setApprovalForAll(address(0xBEEF), false); assertFalse(token.isApprovedForAll(address(this), address(0xBEEF))); } + + /// @notice Tests Transfer From + /// @notice sbf is coming to steal your tokens + function testTransferFrom(address sbf) public { + address from = address(0xABCD); + vm.assume(sbf != from); + vm.assume(sbf != address(0)); + + // Mint the from a token + token.mint(from, 1337); + + + // Non owner cannot transfer + vm.expectRevert(bytes("WRONG_FROM")); + token.transferFrom(sbf, address(this), 1337); + + // sbf can't steal our tokens + vm.startPrank(sbf); + vm.expectRevert(bytes("NOT_AUTHORIZED")); + token.transferFrom(from, sbf, 1337); + vm.stopPrank(); + + // Prank from + vm.startPrank(from); + + // Cannot transfer to 0 + vm.expectRevert(bytes("INVALID_RECIPIENT")); + token.transferFrom(from, address(0), 1337); + + // The owner can transfer it! + token.transferFrom(from, address(0xBEEF), 1337); + vm.stopPrank(); + + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(0xBEEF)); + assertEq(token.balanceOf(address(0xBEEF)), 1); + assertEq(token.balanceOf(from), 0); + } + + /// @notice Tests Transferring from yourself + function testTransferFromSelf(address minter) public { + vm.assume(minter != address(0)); + + // Mint a token + token.mint(minter, 1337); + + // Transfer from self + vm.prank(minter); + token.transferFrom(minter, address(0xBEEF), 1337); + + // Check that it worked + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(0xBEEF)); + assertEq(token.balanceOf(address(0xBEEF)), 1); + assertEq(token.balanceOf(address(this)), 0); + } + + function testTransferFromApproveAll(address from) public { + vm.assume(from != address(0)); + + // Mint a token + token.mint(from, 1337); + + // Give this contract approval to transfer + vm.prank(from); + token.setApprovalForAll(address(this), true); + + // The approved address can transfer + token.transferFrom(from, address(0xBEEF), 1337); + + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(0xBEEF)); + assertEq(token.balanceOf(address(0xBEEF)), 1); + assertEq(token.balanceOf(from), 0); + } } From bc77f121259dbe382fd801f7d9a2923f462c9eef Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Sep 2022 20:09:34 -0400 Subject: [PATCH 16/27] safe transfer from --- src/tokens/ERC721.huff | 224 ++++++++++++++++++--------- test/tokens/ERC721.t.sol | 66 ++++++++ test/tokens/mocks/ERC721Wrapper.huff | 34 ++-- 3 files changed, 239 insertions(+), 85 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index a51340c8..d444eef8 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -19,6 +19,8 @@ #define function transfer(address,uint256) nonpayable returns () #define function transferFrom(address,address,uint256) nonpayable returns () +#define function safeTransferFrom(address,address,uint256) nonpayable returns () + #define function approve(address,uint256) nonpayable returns () #define function setApprovalForAll(address,bool) nonpayable returns () @@ -33,6 +35,8 @@ #define event Approval(address,address,uint256) #define event ApprovalForAll(address,address,bool) +#define event LogWord(address) + // Storage Slots #define constant OWNER_LOCATION = FREE_STORAGE_POINTER() // ownerOf #define constant BALANCE_LOCATION = FREE_STORAGE_POINTER() // balanceOf @@ -223,75 +227,6 @@ /// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// -/// @notice Transfer From -#define macro TRANSFER_TAKE_FROM() = takes(3) returns (3) { - // Input stack: [from, to, tokenId] - - // If from !== ownerOf[tokenId] revert with "WRONG_FROM" - dup1 dup4 // [tokenId, from, from, to, tokenId] - [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, from, from, to, tokenId] - eq cont jumpi // [from, to, tokenId] - WRONG_FROM(0x00) - cont: - - // If to === address(0) revert with "INVALID_RECIPIENT" - dup2 iszero iszero continue jumpi // [from, to, tokenId] - INVALID_RECIPIENT(0x00) - continue: - - // Check if msg.sender == from - dup1 caller eq // [msg.sender == from, from, to, tokenId] - is_authorized jumpi // [from, to, tokenId] - - // Check if approved for all - caller dup2 // [from, msg.sender, from, to, tokenId] - LOAD_ELEMENT_FROM_KEYS(0x00) // [is_approved_for_all, from, to, tokenId] - is_authorized jumpi // [from, to, tokenId] - - // Check if approved for tokenId - dup3 // [tokenId, from, to, tokenId] - [SINGLE_APPROVAL_LOCATION] // [SINGLE_APPROVAL_LOCATION, tokenId, from, to, tokenId] - LOAD_ELEMENT_FROM_KEYS(0x00) // [address_approved_for_tokenId, from, to, tokenId] - caller eq is_authorized jumpi // [from, to, tokenId] - - // If msg.sender != from && !isApprovedForAll[from][msg.sender] && msg.sender != getApproved[id], - UNAUTHORIZED(0x00) - - is_authorized: - - // Update balance of from - 0x01 dup2 // [from, 1, from, to, tokenId] - [BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, 1, from, to, tokenId] - sub dup2 // [from, balance-1, from, to, tokenId] - [BALANCE_LOCATION] - STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] -} - -#define macro TRANSFER_GIVE_TO() = takes(3) returns (3) { - // retrieve balance - // input stack: // [from, to, tokenId] - dup2 // [to, from, to, tokenId] - [BALANCE_LOCATION] - LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, from, to, tokenId] - 0x01 add // [balance+1, from, to, tokenId] - - // update balance - dup3 // [to, balance+1, from, to, tokenId] - [BALANCE_LOCATION] - STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] - - // update ownerOf - dup2 // [to, from, to, tokenId] - dup4 // [tokenId, to, from, to, tokenId] - [OWNER_LOCATION] - STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] - - // update approval - 0x00 dup4 // [tokenId, address(0), from, to, tokenId] - [SINGLE_APPROVAL_LOCATION] - STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] -} - /// @notice Approve /// @notice Approves a spender for a specific token #define macro APPROVE() = takes (0) returns (0) { @@ -367,10 +302,157 @@ stop } +/// @notice Safe Transfer From #define macro SAFE_TRANSFER_FROM() = takes (0) returns (0) { - 0x00 0x00 revert + // Setup the stack for the transfer function. + 0x44 calldataload // [tokenId] + 0x24 calldataload // [to, tokenId] + 0x04 calldataload // [from, to, tokenId] + + TRANSFER_TAKE_FROM() // [from, to, tokenId] + TRANSFER_GIVE_TO() // [from, to, tokenId] + + // Emit the transfer event. + __EVENT_HASH(Transfer) // [sig,from, to, tokenId] + 0x20 0x00 // [0, 0, sig, from, to, tokenId] + log4 // [] + + // Make sure we can transfer to the recipient + 0x24 calldataload // [to] + dup1 extcodesize // [to.code.length, to] + iszero safe jumpi // [to] + + // onERC721Received Selector + // 0xf0b9e5ba + 0x150b7a02 dup1 // [onERC721Received, onERC721Received, to] + 0xE0 shl // [onERC721Received_shifted, onERC721Received, to] + + // TODO: REMOVE LOG + dup1 __EVENT_HASH(LogWord) + 0x00 0x00 log2 + // TODO: REMOVE LOG + + // Store the left-shifted selector for call + 0x20 mstore // [onERC721Received, to] + + // Store the msg.sender as the first arg + caller 0x24 mstore // [onERC721Received, to] + + // Store from as the second arg + 0x04 calldataload // [from, onERC721Received, to] + 0x44 mstore // [onERC721Received, to] + + // Id is the third arg + 0x44 calldataload // [tokenId, onERC721Received, to] + 0x64 mstore // [onERC721Received, to] + + // Clean scratch space for return data use + 0x00 dup1 mstore // [onERC721Received, to] + + // Call address(to).onERC721Received(msg.sender, from, tokenId, "") + 0x20 // [retSize, onERC721Received, to] + 0x00 // [retOffset, retSize, onERC721Received, to] + 0x60 // [argSize, retOffset, retSize, onERC721Received, to] + 0x20 // [argOffset, argSize, retOffset, retSize, onERC721Received, to] + 0x00 // [value, argOffset, argSize, retOffset, retSize, onERC721Received, to] + dup7 // [to, value, argOffset, argSize, retOffset, retSize, onERC721Received, to] + gas // [gas, to, value, argOffset, argSize, retOffset, retSize, onERC721Received, to] + call // [success, onERC721Received, to] + + // TODO: REMOVE LOG + dup1 __EVENT_HASH(LogWord) + 0x00 0x00 log2 + // TODO: REMOVE LOG + + // Revert if call isn't successful + iszero iszero // [is_success, onERC721Received, to] + cont jumpi // [onERC721Received, to] + 0x00 dup1 revert + cont: + + // Compare the return data to the onERC721Received selector + 0x00 mload 0xE0 shr // [response, onERC721Received, to] + eq safe jumpi // [to] + + // Revert if the return data is not accepted + UNSAFE_RECIPIENT(0x00) + + safe: + stop } #define macro SAFE_TRANSFER_FROM_WITH_DATA() = takes (0) returns (0) { 0x00 0x00 revert -} \ No newline at end of file +} + +/// >>>>>>>>>>>>>>>>>>>>> INTERNAL HELPERS <<<<<<<<<<<<<<<<<<<<<< /// + +/// @notice Internal Macro to update Transfer from accounting +#define macro TRANSFER_TAKE_FROM() = takes (3) returns (3) { + // Input stack: [from, to, tokenId] + + // If from !== ownerOf[tokenId] revert with "WRONG_FROM" + dup1 dup4 // [tokenId, from, from, to, tokenId] + [OWNER_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [owner, from, from, to, tokenId] + eq cont jumpi // [from, to, tokenId] + WRONG_FROM(0x00) + cont: + + // If to === address(0) revert with "INVALID_RECIPIENT" + dup2 iszero iszero continue jumpi // [from, to, tokenId] + INVALID_RECIPIENT(0x00) + continue: + + // Check if msg.sender == from + dup1 caller eq // [msg.sender == from, from, to, tokenId] + is_authorized jumpi // [from, to, tokenId] + + // Check if approved for all + caller dup2 // [from, msg.sender, from, to, tokenId] + LOAD_ELEMENT_FROM_KEYS(0x00) // [is_approved_for_all, from, to, tokenId] + is_authorized jumpi // [from, to, tokenId] + + // Check if approved for tokenId + dup3 // [tokenId, from, to, tokenId] + [SINGLE_APPROVAL_LOCATION] // [SINGLE_APPROVAL_LOCATION, tokenId, from, to, tokenId] + LOAD_ELEMENT_FROM_KEYS(0x00) // [address_approved_for_tokenId, from, to, tokenId] + caller eq is_authorized jumpi // [from, to, tokenId] + + // If msg.sender != from && !isApprovedForAll[from][msg.sender] && msg.sender != getApproved[id], + UNAUTHORIZED(0x00) + + is_authorized: + + // Update balance of from + 0x01 dup2 // [from, 1, from, to, tokenId] + [BALANCE_LOCATION] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, 1, from, to, tokenId] + sub dup2 // [from, balance-1, from, to, tokenId] + [BALANCE_LOCATION] + STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] +} + +/// @notice Internal Macro to update Transfer to accounting +#define macro TRANSFER_GIVE_TO() = takes (3) returns (3) { + // retrieve balance + // input stack: // [from, to, tokenId] + dup2 // [to, from, to, tokenId] + [BALANCE_LOCATION] + LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, from, to, tokenId] + 0x01 add // [balance+1, from, to, tokenId] + + // update balance + dup3 // [to, balance+1, from, to, tokenId] + [BALANCE_LOCATION] + STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] + + // update ownerOf + dup2 // [to, from, to, tokenId] + dup4 // [tokenId, to, from, to, tokenId] + [OWNER_LOCATION] + STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] + + // update approval + 0x00 dup4 // [tokenId, address(0), from, to, tokenId] + [SINGLE_APPROVAL_LOCATION] + STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] +} diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index 11c54d8e..d4428fe7 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -4,6 +4,29 @@ pragma solidity ^0.8.15; import "forge-std/Test.sol"; import "foundry-huff/HuffDeployer.sol"; +import { ERC721TokenReceiver } from "solmate/tokens/ERC721.sol"; + +contract ERC721Recipient is ERC721TokenReceiver { + address public operator; + address public from; + uint256 public id; + bytes public data; + + function onERC721Received( + address _operator, + address _from, + uint256 _id, + bytes calldata _data + ) public virtual override returns (bytes4) { + operator = _operator; + from = _from; + id = _id; + data = _data; + + return ERC721TokenReceiver.onERC721Received.selector; + } +} + interface IERC721 { function name() external returns (string memory); function symbol() external returns (string memory); @@ -14,6 +37,8 @@ interface IERC721 { function transfer(address, uint256) external; function transferFrom(address, address, uint256) external; + function safeTransferFrom(address, address, uint256) external; + function approve(address, uint256) external; function setApprovalForAll(address, bool) external; @@ -182,4 +207,45 @@ contract ERC721Test is Test { assertEq(token.balanceOf(address(0xBEEF)), 1); assertEq(token.balanceOf(from), 0); } + + function testSafeTransferFromToEOA(address from) public { + vm.assume(from != address(0)); + vm.assume(from != address(0xBEEF)); + + token.mint(from, 1337); + + vm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, address(0xBEEF), 1337); + + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(0xBEEF)); + assertEq(token.balanceOf(address(0xBEEF)), 1); + assertEq(token.balanceOf(from), 0); + } + + function testSafeTransferFromToERC721Recipient(address from) public { + vm.assume(from != address(0)); + + ERC721Recipient recipient = new ERC721Recipient(); + console2.logBytes4(ERC721Recipient.onERC721Received.selector); + + token.mint(from, 1337); + + vm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, address(recipient), 1337); + + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(recipient)); + assertEq(token.balanceOf(address(recipient)), 1); + assertEq(token.balanceOf(from), 0); + + assertEq(recipient.operator(), address(this)); + assertEq(recipient.from(), from); + assertEq(recipient.id(), 1337); + assertEq(keccak256(abi.encode(recipient.data())), keccak256(abi.encode(""))); + } } diff --git a/test/tokens/mocks/ERC721Wrapper.huff b/test/tokens/mocks/ERC721Wrapper.huff index 1733ebdc..85a728e1 100644 --- a/test/tokens/mocks/ERC721Wrapper.huff +++ b/test/tokens/mocks/ERC721Wrapper.huff @@ -6,6 +6,8 @@ #define function mint(address,uint256) nonpayable returns () #define function transfer(address,uint256) nonpayable returns () #define function transferFrom(address,address,uint256) nonpayable returns () +#define function safeTransferFrom(address,address,uint256) nonpayable returns () + #define function approve(address,uint256) nonpayable returns () #define function setApprovalForAll(address,bool) nonpayable returns () @@ -18,8 +20,6 @@ #define function mint(address, uint256) payable returns () #define function burn(uint256) nonpayable returns () -/// >>>>>>>>>>>>>>>>>>>>> EXTERNAL FUNCTIONS <<<<<<<<<<<<<<<<<<<<<< /// - #define macro MINT() = takes (0) returns (0) { 0x24 calldataload // [tokenId] 0x04 calldataload // [to, tokenId] @@ -40,20 +40,23 @@ dup1 __FUNC_SIG(burn) eq burn_jump jumpi dup1 __FUNC_SIG(approve) eq approve jumpi + dup1 __FUNC_SIG(setApprovalForAll) eq setApprovalForAll jumpi + dup1 __FUNC_SIG(transferFrom) eq transferFrom jumpi dup1 __FUNC_SIG(safeTransferFrom) eq safeTransferFrom jumpi dup1 __FUNC_SIG(safeTransferFromWithData) eq safeTransferFromWithData jumpi - dup1 __FUNC_SIG(setApprovalForAll) eq setApprovalForAll jumpi - dup1 __FUNC_SIG(getApproved) eq getApproved jumpi - dup1 __FUNC_SIG(balanceOf) eq balanceOf jumpi - dup1 __FUNC_SIG(ownerOf) eq ownerOf jumpi dup1 __FUNC_SIG(name) eq name jumpi dup1 __FUNC_SIG(symbol) eq symbol jumpi dup1 __FUNC_SIG(tokenURI) eq tokenURI jumpi dup1 __FUNC_SIG(supportsInterface) eq supportsInterface jumpi + + dup1 __FUNC_SIG(getApproved) eq getApproved jumpi dup1 __FUNC_SIG(isApprovedForAll) eq isApprovedForAll jumpi + dup1 __FUNC_SIG(balanceOf) eq balanceOf jumpi + dup1 __FUNC_SIG(ownerOf) eq ownerOf jumpi + // Revert on failed dispatch 0x00 dup1 revert @@ -62,31 +65,34 @@ burn_jump: BURN() + approve: + APPROVE() + setApprovalForAll: + SET_APPROVAL_FOR_ALL() + transferFrom: TRANSFER_FROM() safeTransferFrom: SAFE_TRANSFER_FROM() // not implemented yet safeTransferFromWithData: SAFE_TRANSFER_FROM_WITH_DATA() // not implemented yet - approve: - APPROVE() - setApprovalForAll: - SET_APPROVAL_FOR_ALL() - getApproved: - GET_APPROVED() name: NAME() symbol: SYMBOL() - balanceOf: - BALANCE_OF() tokenURI: TOKEN_URI() // not implemented yet supportsInterface: SUPPORTS_INTERFACE() + + getApproved: + GET_APPROVED() isApprovedForAll: IS_APPROVED_FOR_ALL() + + balanceOf: + BALANCE_OF() ownerOf: OWNER_OF() } \ No newline at end of file From afa7f454da0f9768e773f8c8d06d6c397e7aa3af Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Sep 2022 21:35:56 -0400 Subject: [PATCH 17/27] consistent naming --- foundry.toml | 16 ++++---- src/tokens/ERC1155.huff | 13 +++--- src/tokens/MockERC20.huff | 23 ----------- test/data-structures/Arrays.t.sol | 2 +- test/data-structures/Hashmap.t.sol | 2 +- ...antiatedArrays.huff => ArrayWrappers.huff} | 0 ...iatedHashmap.huff => HashmapWrappers.huff} | 0 test/tokens/ERC20.t.sol | 4 +- test/tokens/ERC721.t.sol | 2 +- test/tokens/mocks/ERC1155Wrappers.huff | 40 +++++++++++-------- ...ntable.huff => ERC20MintableWrappers.huff} | 0 test/tokens/mocks/ERC20Wrappers.huff | 5 +++ ...ERC721Wrapper.huff => ERC721Wrappers.huff} | 21 ---------- test/tokens/mocks/MockERC20.huff | 21 ---------- test/utils/Errors.t.sol | 2 +- test/utils/LibBit.t.sol | 2 +- test/utils/ReentrancyGuard.t.sol | 2 +- .../{ErrorsMock.huff => ErrorWrappers.huff} | 0 ...LibBitWrapper.huff => LibBitWrappers.huff} | 0 ...Mock.huff => ReentrancyGuardWrappers.huff} | 0 20 files changed, 51 insertions(+), 104 deletions(-) delete mode 100644 src/tokens/MockERC20.huff rename test/data-structures/mocks/{InstantiatedArrays.huff => ArrayWrappers.huff} (100%) rename test/data-structures/mocks/{InstantiatedHashmap.huff => HashmapWrappers.huff} (100%) rename test/tokens/mocks/{ERC20Mintable.huff => ERC20MintableWrappers.huff} (100%) create mode 100644 test/tokens/mocks/ERC20Wrappers.huff rename test/tokens/mocks/{ERC721Wrapper.huff => ERC721Wrappers.huff} (68%) delete mode 100644 test/tokens/mocks/MockERC20.huff rename test/utils/mocks/{ErrorsMock.huff => ErrorWrappers.huff} (100%) rename test/utils/mocks/{LibBitWrapper.huff => LibBitWrappers.huff} (100%) rename test/utils/mocks/{ReentrancyGuardMock.huff => ReentrancyGuardWrappers.huff} (100%) diff --git a/foundry.toml b/foundry.toml index a1ff4fd2..28edca6c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,8 +4,8 @@ libs = ['lib'] ffi = true fuzz_runs = 1000 fs_permissions = [ - { access = "read", path = "./test/data-structures/mocks/InstantiatedArrays.huff" }, - { access = "read", path = "./test/data-structures/mocks/InstantiatedHashmap.huff" }, + { access = "read", path = "./test/data-structures/mocks/ArrayWrappers.huff" }, + { access = "read", path = "./test/data-structures/mocks/HashmapWrappers.huff" }, { access = "read", path = "./test/math/mocks/FixedPointMathWrappers.huff" }, { access = "read", path = "./test/math/mocks/SafeMathWrappers.huff" }, @@ -13,17 +13,17 @@ fs_permissions = [ { access = "read", path = "./test/math/mocks/TrigonometryWrappers.huff" }, { access = "read", path = "./test/tokens/mocks/ERC1155Wrappers.huff" }, - { access = "read", path = "./test/tokens/mocks/MockERC20.huff" }, - { access = "read", path = "./test/tokens/mocks/ERC721Wrapper.huff" }, - { access = "read", path = "./test/tokens/mocks/ERC20Mintable.huff" }, + { access = "read", path = "./test/tokens/mocks/ERC20Wrappers.huff" }, + { access = "read", path = "./test/tokens/mocks/ERC721Wrappers.huff" }, + { access = "read", path = "./test/tokens/mocks/ERC20MintableWrappers.huff" }, { access = "read", path = "./test/utils/mocks/BitPackLibWrappers.huff" }, - { access = "read", path = "./test/utils/mocks/ErrorsMock.huff" }, + { access = "read", path = "./test/utils/mocks/ErrorWrappers.huff" }, { access = "read", path = "./test/utils/mocks/JumpTableUtilWrappers.huff" }, { access = "read", path = "./test/utils/mocks/MerkleProofLibWrappers.huff" }, { access = "read", path = "./test/utils/mocks/MulticallableWrappers.huff" }, - { access = "read", path = "./test/utils/mocks/ReentrancyGuardMock.huff" }, - { access = "read", path = "./test/utils/mocks/LibBitWrapper.huff" }, + { access = "read", path = "./test/utils/mocks/ReentrancyGuardWrappers.huff" }, + { access = "read", path = "./test/utils/mocks/LibBitWrappers.huff" }, ] remappings = [ "forge-std/=lib/forge-std/src/", diff --git a/src/tokens/ERC1155.huff b/src/tokens/ERC1155.huff index 1339d49a..32ba99fd 100644 --- a/src/tokens/ERC1155.huff +++ b/src/tokens/ERC1155.huff @@ -8,20 +8,19 @@ #include "../utils/ERC1155Receiver.huff" #include "../utils/CommonErrors.huff" -// Stateful Function Interface +// Function Interface #define function mint(address,uint256,uint256,bytes) nonpayable returns () -#define function batchMint(address,uint256[],uint256[],bytes) nonpayable returns () - #define function burn(address,uint256,uint256) nonpayable returns () + +#define function batchMint(address,uint256[],uint256[],bytes) nonpayable returns () #define function batchBurn(address,uint256[],uint256[]) nonpayable returns () +#define function isApprovedForAll(address,address) view returns (bool) +#define function setApprovalForAll(address,bool) nonpayable returns () + #define function safeTransferFrom(address,address,uint256,uint256,bytes) nonpayable returns () #define function safeBatchTransferFrom(address,address,uint256[],uint256[],bytes) nonpayable returns() -#define function setApprovalForAll(address,bool) nonpayable returns () -// Accessor Function Interface -#define function getApproved(uint256) view returns (address) -#define function isApprovedForAll(address,address) view returns (bool) #define function balanceOf(address,uint256) view returns (uint256) #define function balanceOfBatch(address[],uint256[]) view returns (uint256[]) diff --git a/src/tokens/MockERC20.huff b/src/tokens/MockERC20.huff deleted file mode 100644 index 45d026ed..00000000 --- a/src/tokens/MockERC20.huff +++ /dev/null @@ -1,23 +0,0 @@ - -// !! WARNING !! -// This code is meant to be appended to src/tokens/ERC20.huff -// Thus, it does not import any other files - -#include "./ERC20.huff" - -// Constructor -// #define macro CONSTRUCTOR() { -// // Construct the ERC20 -// CONSTRUCTOR_ERC20() - -// // Mint the sender a supply -// caller // [to] -// 0x100 // [value, to] -// _MINT() // [] -// } - -// Main Entrypoint -#define macro MAIN() = { - 0x00 calldataload 0xE0 shr // [func sig] - MAIN_ERC20() -} diff --git a/test/data-structures/Arrays.t.sol b/test/data-structures/Arrays.t.sol index ff639260..a7206e24 100644 --- a/test/data-structures/Arrays.t.sol +++ b/test/data-structures/Arrays.t.sol @@ -19,7 +19,7 @@ contract ArraysTest is Test { function setUp() public { // Read instantiable arrays from file string memory instantiable_code = vm.readFile( - "test/data-structures/mocks/InstantiatedArrays.huff" + "test/data-structures/mocks/ArrayWrappers.huff" ); // Create an Instantiable Arrays diff --git a/test/data-structures/Hashmap.t.sol b/test/data-structures/Hashmap.t.sol index da6f3d81..927594ac 100644 --- a/test/data-structures/Hashmap.t.sol +++ b/test/data-structures/Hashmap.t.sol @@ -17,7 +17,7 @@ contract HashmapTest is Test { function setUp() public { // Read instantiable hashmap from file - string memory instantiable_code = vm.readFile("test/data-structures/mocks/InstantiatedHashmap.huff"); + string memory instantiable_code = vm.readFile("test/data-structures/mocks/HashmapWrappers.huff"); // Create an Instantiable Hashmap HuffConfig config = HuffDeployer.config().with_code(instantiable_code); diff --git a/test/data-structures/mocks/InstantiatedArrays.huff b/test/data-structures/mocks/ArrayWrappers.huff similarity index 100% rename from test/data-structures/mocks/InstantiatedArrays.huff rename to test/data-structures/mocks/ArrayWrappers.huff diff --git a/test/data-structures/mocks/InstantiatedHashmap.huff b/test/data-structures/mocks/HashmapWrappers.huff similarity index 100% rename from test/data-structures/mocks/InstantiatedHashmap.huff rename to test/data-structures/mocks/HashmapWrappers.huff diff --git a/test/tokens/ERC20.t.sol b/test/tokens/ERC20.t.sol index cfac1b6c..fe883480 100644 --- a/test/tokens/ERC20.t.sol +++ b/test/tokens/ERC20.t.sol @@ -48,8 +48,8 @@ contract ERC20Test is Test { function setUp() public { // Read our wrappers - string memory mintable_wrapper = vm.readFile("test/tokens/mocks/ERC20Mintable.huff"); - string memory mock_wrapper = vm.readFile("test/tokens/mocks/MockERC20.huff"); + string memory mintable_wrapper = vm.readFile("test/tokens/mocks/ERC20MintableWrappers.huff"); + string memory mock_wrapper = vm.readFile("test/tokens/mocks/ERC20Wrappers.huff"); // Deploy the Mintable ERC20 vm.startPrank(deployer); diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index d4428fe7..270bc374 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -54,7 +54,7 @@ contract ERC721Test is Test { function setUp() public { // Deploy the ERC721 - string memory wrapper_code = vm.readFile("test/tokens/mocks/ERC721Wrapper.huff"); + string memory wrapper_code = vm.readFile("test/tokens/mocks/ERC721Wrappers.huff"); token = IERC721(HuffDeployer.config().with_code(wrapper_code).deploy("tokens/ERC721")); } diff --git a/test/tokens/mocks/ERC1155Wrappers.huff b/test/tokens/mocks/ERC1155Wrappers.huff index 4eaec38f..488b71b0 100644 --- a/test/tokens/mocks/ERC1155Wrappers.huff +++ b/test/tokens/mocks/ERC1155Wrappers.huff @@ -3,48 +3,56 @@ 0x00 calldataload 0xE0 shr // [function selector on stack] dup1 __FUNC_SIG(mint) eq mint jumpi + dup1 __FUNC_SIG(burn) eq burn jumpi + dup1 __FUNC_SIG(batchMint) eq batchMint jumpi + dup1 __FUNC_SIG(batchBurn) eq batchBurn jumpi + dup1 __FUNC_SIG(safeTransferFrom) eq safeTransferFrom jumpi dup1 __FUNC_SIG(safeBatchTransferFrom) eq batchSafeTransferFrom jumpi + + dup1 __FUNC_SIG(isApprovedForAll) eq isApprovedForAll jumpi dup1 __FUNC_SIG(setApprovalForAll) eq setApprovalForAll jumpi - dup1 __FUNC_SIG(burn) eq burn jumpi - dup1 __FUNC_SIG(batchBurn) eq batchBurn jumpi + dup1 __FUNC_SIG(balanceOf) eq balanceOf jumpi - dup1 __FUNC_SIG(isApprovedForAll) eq isApprovedForAll jumpi + dup1 __FUNC_SIG(balanceOfBatch) eq balanceOfBatch jumpi + dup1 __FUNC_SIG(name) eq name jumpi dup1 __FUNC_SIG(symbol) eq symbol jumpi dup1 __FUNC_SIG(supportsInterface) eq supportsInterface jumpi - dup1 __FUNC_SIG(symbol) eq symbol jumpi - dup1 __FUNC_SIG(getName) eq name jumpi - dup1 __FUNC_SIG(balanceOfBatch) eq balanceOfBatch jumpi // No fallback function 0x00 dup1 revert mint: MINT() - safeTransferFrom: - SAFE_TRANSFER_FROM() - batchSafeTransferFrom: - BATCH_SAFE_TRANSFER_FROM() burn: BURN() + batchMint: BATCH_MINT() - setApprovalForAll: - SET_APPROVAL_FOR_ALL() batchBurn: BATCH_BURN() - supportsInterface: - SUPPORTS_INTERFACE() + + safeTransferFrom: + SAFE_TRANSFER_FROM() + batchSafeTransferFrom: + BATCH_SAFE_TRANSFER_FROM() + + isApprovedForAll: + IS_APPROVED_FOR_ALL() + setApprovalForAll: + SET_APPROVAL_FOR_ALL() + balanceOf: BALANCE_OF() balanceOfBatch: BALANCE_OF_BATCH() - isApprovedForAll: - IS_APPROVED_FOR_ALL() + name: NAME() symbol: SYMBOL() + supportsInterface: + SUPPORTS_INTERFACE() } diff --git a/test/tokens/mocks/ERC20Mintable.huff b/test/tokens/mocks/ERC20MintableWrappers.huff similarity index 100% rename from test/tokens/mocks/ERC20Mintable.huff rename to test/tokens/mocks/ERC20MintableWrappers.huff diff --git a/test/tokens/mocks/ERC20Wrappers.huff b/test/tokens/mocks/ERC20Wrappers.huff new file mode 100644 index 00000000..08d3027f --- /dev/null +++ b/test/tokens/mocks/ERC20Wrappers.huff @@ -0,0 +1,5 @@ + +#define macro MAIN() = { + 0x00 calldataload 0xE0 shr // [func sig] + MAIN_ERC20() +} diff --git a/test/tokens/mocks/ERC721Wrapper.huff b/test/tokens/mocks/ERC721Wrappers.huff similarity index 68% rename from test/tokens/mocks/ERC721Wrapper.huff rename to test/tokens/mocks/ERC721Wrappers.huff index 85a728e1..7cc82c37 100644 --- a/test/tokens/mocks/ERC721Wrapper.huff +++ b/test/tokens/mocks/ERC721Wrappers.huff @@ -1,24 +1,3 @@ -// Interface -#define function name() nonpayable returns (string) -#define function symbol() nonpayable returns (string) -#define function tokenURI(uint256) nonpayable returns (string) - -#define function mint(address,uint256) nonpayable returns () -#define function transfer(address,uint256) nonpayable returns () -#define function transferFrom(address,address,uint256) nonpayable returns () -#define function safeTransferFrom(address,address,uint256) nonpayable returns () - -#define function approve(address,uint256) nonpayable returns () -#define function setApprovalForAll(address,bool) nonpayable returns () - -#define function getApproved(uint256) view returns (address) -#define function isApprovedForAll(address,address) view returns (bool) -#define function ownerOf(uint256) view returns (address) -#define function balanceOf(address) view returns (uint256) -#define function supportsInterface(bytes4) view returns (bool) - -#define function mint(address, uint256) payable returns () -#define function burn(uint256) nonpayable returns () #define macro MINT() = takes (0) returns (0) { 0x24 calldataload // [tokenId] diff --git a/test/tokens/mocks/MockERC20.huff b/test/tokens/mocks/MockERC20.huff deleted file mode 100644 index c620f27b..00000000 --- a/test/tokens/mocks/MockERC20.huff +++ /dev/null @@ -1,21 +0,0 @@ - -// !! WARNING !! -// This code is meant to be appended to src/tokens/ERC20.huff -// Thus, it does not import any other files - -// Constructor -// #define macro CONSTRUCTOR() { -// // Construct the ERC20 -// CONSTRUCTOR_ERC20() - -// // Mint the sender a supply -// caller // [to] -// 0x100 // [value, to] -// _MINT() // [] -// } - -// Main Entrypoint -#define macro MAIN() = { - 0x00 calldataload 0xE0 shr // [func sig] - MAIN_ERC20() -} diff --git a/test/utils/Errors.t.sol b/test/utils/Errors.t.sol index 2af78a1c..a87311a8 100644 --- a/test/utils/Errors.t.sol +++ b/test/utils/Errors.t.sol @@ -51,7 +51,7 @@ contract ErrorsTest is Test { IErrorsMock eLib; function setUp() public { - string memory wrapper_code = vm.readFile("test/utils/mocks/ErrorsMock.huff"); + string memory wrapper_code = vm.readFile("test/utils/mocks/ErrorWrappers.huff"); eLib = IErrorsMock(HuffDeployer.deploy_with_code("utils/Errors", wrapper_code)); } diff --git a/test/utils/LibBit.t.sol b/test/utils/LibBit.t.sol index be71caa2..c1520d1a 100644 --- a/test/utils/LibBit.t.sol +++ b/test/utils/LibBit.t.sol @@ -16,7 +16,7 @@ contract LibBitTest is Test { function setUp() public { /// @notice deploy a new instance of IJumpTableUtil by /// passing in the address of the deployed Huff contract - string memory wrapper_code = vm.readFile("test/utils/mocks/LibBitWrapper.huff"); + string memory wrapper_code = vm.readFile("test/utils/mocks/LibBitWrappers.huff"); lib = ILibBit(HuffDeployer.deploy_with_code("utils/LibBit", wrapper_code)); } diff --git a/test/utils/ReentrancyGuard.t.sol b/test/utils/ReentrancyGuard.t.sol index ce7f1f77..e5ca2641 100644 --- a/test/utils/ReentrancyGuard.t.sol +++ b/test/utils/ReentrancyGuard.t.sol @@ -15,7 +15,7 @@ contract ReentranctGuardTest is Test { IGuard guard; function setUp() public { - string memory wrapper_code = vm.readFile("test/utils/mocks/ReentrancyGuardMock.huff"); + string memory wrapper_code = vm.readFile("test/utils/mocks/ReentrancyGuardWrappers.huff"); guard = IGuard(HuffDeployer.deploy_with_code("utils/ReentrancyGuard", wrapper_code)); } diff --git a/test/utils/mocks/ErrorsMock.huff b/test/utils/mocks/ErrorWrappers.huff similarity index 100% rename from test/utils/mocks/ErrorsMock.huff rename to test/utils/mocks/ErrorWrappers.huff diff --git a/test/utils/mocks/LibBitWrapper.huff b/test/utils/mocks/LibBitWrappers.huff similarity index 100% rename from test/utils/mocks/LibBitWrapper.huff rename to test/utils/mocks/LibBitWrappers.huff diff --git a/test/utils/mocks/ReentrancyGuardMock.huff b/test/utils/mocks/ReentrancyGuardWrappers.huff similarity index 100% rename from test/utils/mocks/ReentrancyGuardMock.huff rename to test/utils/mocks/ReentrancyGuardWrappers.huff From 99dc72dd98c952bd706ddfa14bf249c401f1ae13 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Sep 2022 22:04:03 -0400 Subject: [PATCH 18/27] fixes --- src/tokens/ERC721.huff | 2 +- src/utils/NonPayable.huff | 2 ++ test/tokens/ERC721.t.sol | 2 +- test/tokens/mocks/ERC20MintableWrappers.huff | 4 ++-- test/tokens/mocks/ERC721Wrappers.huff | 7 +++++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index d444eef8..991c84c9 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -5,9 +5,9 @@ /// @notice Adapted from Solmate https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol // Imports +#include "../data-structures/Hashmap.huff" #include "../utils/CommonErrors.huff" #include "../utils/NonPayable.huff" -#include "../data-structures/Hashmap.huff" // Interface #define function name() nonpayable returns (string) diff --git a/src/utils/NonPayable.huff b/src/utils/NonPayable.huff index e450e797..a958ab04 100644 --- a/src/utils/NonPayable.huff +++ b/src/utils/NonPayable.huff @@ -2,6 +2,8 @@ /// @author asnared /// @notice Simple macro to revert if a call has a value +#include "./Errors.huff" + // "NON_PAYABLE" Revert Message String #define constant NON_PAYABLE_ERROR = 0xb4e4f4e5f50415941424c45000000000000000000000000000000000000000000 #define constant NON_PAYABLE_LENGTH = 0x0b diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index 270bc374..c6766f2f 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -59,7 +59,7 @@ contract ERC721Test is Test { } /// @notice Test the ERC721 Metadata - function invariantMetadata() public { + function testMetadata() public { assertEq(keccak256(abi.encode(token.name())), keccak256(abi.encode("Token"))); assertEq(keccak256(abi.encode(token.symbol())), keccak256(abi.encode("TKN"))); } diff --git a/test/tokens/mocks/ERC20MintableWrappers.huff b/test/tokens/mocks/ERC20MintableWrappers.huff index 5b688cab..a4fc549d 100644 --- a/test/tokens/mocks/ERC20MintableWrappers.huff +++ b/test/tokens/mocks/ERC20MintableWrappers.huff @@ -2,7 +2,7 @@ #define function mint(address, uint256) nonpayable returns () #define function burn(address, uint256) nonpayable returns () -#define macro BURN() = takes(0) returns (0) { +#define macro BURN() = takes (0) returns (0) { NON_PAYABLE() // Setup the stack for the burn function. 0x04 calldataload // [from] @@ -15,7 +15,7 @@ stop // [] } -#define macro MINT() = takes(0) returns (0) { +#define macro MINT() = takes (0) returns (0) { NON_PAYABLE() // Setup the stack for the mint function. diff --git a/test/tokens/mocks/ERC721Wrappers.huff b/test/tokens/mocks/ERC721Wrappers.huff index 7cc82c37..69f63418 100644 --- a/test/tokens/mocks/ERC721Wrappers.huff +++ b/test/tokens/mocks/ERC721Wrappers.huff @@ -1,4 +1,7 @@ +#define function mint(address, uint256) payable returns () +#define function burn(uint256) nonpayable returns () + #define macro MINT() = takes (0) returns (0) { 0x24 calldataload // [tokenId] 0x04 calldataload // [to, tokenId] @@ -11,7 +14,7 @@ } // Function Dispatch -#define macro MAIN() = takes(0) returns (0) { +#define macro MAIN() = takes (0) returns (0) { 0x00 calldataload 0xE0 shr // [sig] // Mint and Burning Functions @@ -52,7 +55,7 @@ transferFrom: TRANSFER_FROM() safeTransferFrom: - SAFE_TRANSFER_FROM() // not implemented yet + SAFE_TRANSFER_FROM() safeTransferFromWithData: SAFE_TRANSFER_FROM_WITH_DATA() // not implemented yet From 6520112e6aa20411002153935cce55c2442c4e9a Mon Sep 17 00:00:00 2001 From: clabby Date: Tue, 13 Sep 2022 22:34:27 -0400 Subject: [PATCH 19/27] Add blank `bytes` object as 4th arg to `onERC721Received` --- src/tokens/ERC721.huff | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 991c84c9..5d2a4975 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -5,9 +5,9 @@ /// @notice Adapted from Solmate https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol // Imports -#include "../data-structures/Hashmap.huff" #include "../utils/CommonErrors.huff" #include "../utils/NonPayable.huff" +#include "../data-structures/Hashmap.huff" // Interface #define function name() nonpayable returns (string) @@ -327,11 +327,6 @@ 0x150b7a02 dup1 // [onERC721Received, onERC721Received, to] 0xE0 shl // [onERC721Received_shifted, onERC721Received, to] - // TODO: REMOVE LOG - dup1 __EVENT_HASH(LogWord) - 0x00 0x00 log2 - // TODO: REMOVE LOG - // Store the left-shifted selector for call 0x20 mstore // [onERC721Received, to] @@ -346,28 +341,26 @@ 0x44 calldataload // [tokenId, onERC721Received, to] 0x64 mstore // [onERC721Received, to] + // Blank bytes array as 4th arg (no data) + 0x80 0x84 mstore + 0x00 0xA4 mstore + // Clean scratch space for return data use 0x00 dup1 mstore // [onERC721Received, to] // Call address(to).onERC721Received(msg.sender, from, tokenId, "") 0x20 // [retSize, onERC721Received, to] 0x00 // [retOffset, retSize, onERC721Received, to] - 0x60 // [argSize, retOffset, retSize, onERC721Received, to] - 0x20 // [argOffset, argSize, retOffset, retSize, onERC721Received, to] - 0x00 // [value, argOffset, argSize, retOffset, retSize, onERC721Received, to] + 0xA4 // [argSize, retOffset, retSize, onERC721Received, to] + dup3 // [argOffset, argSize, retOffset, retSize, onERC721Received, to] + dup3 // [value, argOffset, argSize, retOffset, retSize, onERC721Received, to] dup7 // [to, value, argOffset, argSize, retOffset, retSize, onERC721Received, to] gas // [gas, to, value, argOffset, argSize, retOffset, retSize, onERC721Received, to] call // [success, onERC721Received, to] - // TODO: REMOVE LOG - dup1 __EVENT_HASH(LogWord) - 0x00 0x00 log2 - // TODO: REMOVE LOG - // Revert if call isn't successful - iszero iszero // [is_success, onERC721Received, to] cont jumpi // [onERC721Received, to] - 0x00 dup1 revert + 0x00 dup1 revert cont: // Compare the return data to the onERC721Received selector From b1c0c85a9eac50c7b4b2b8ec4edeb3a426c93531 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Sep 2022 22:39:08 -0400 Subject: [PATCH 20/27] remove log --- src/tokens/ERC721.huff | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 991c84c9..c3afe3d0 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -313,8 +313,8 @@ TRANSFER_GIVE_TO() // [from, to, tokenId] // Emit the transfer event. - __EVENT_HASH(Transfer) // [sig,from, to, tokenId] - 0x20 0x00 // [0, 0, sig, from, to, tokenId] + __EVENT_HASH(Transfer) // [sig, from, to, tokenId] + 0x00 0x00 // [0, 0, sig, from, to, tokenId] log4 // [] // Make sure we can transfer to the recipient @@ -436,23 +436,23 @@ // retrieve balance // input stack: // [from, to, tokenId] dup2 // [to, from, to, tokenId] - [BALANCE_LOCATION] + [BALANCE_LOCATION] // [balance_slot, to, from, to, tokenId] LOAD_ELEMENT_FROM_KEYS(0x00) // [balance, from, to, tokenId] 0x01 add // [balance+1, from, to, tokenId] // update balance dup3 // [to, balance+1, from, to, tokenId] - [BALANCE_LOCATION] + [BALANCE_LOCATION] // [balance_slot, to, balance+1, from, to, tokenId] STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] // update ownerOf dup2 // [to, from, to, tokenId] dup4 // [tokenId, to, from, to, tokenId] - [OWNER_LOCATION] + [OWNER_LOCATION] // [owner_slot, tokenId, to, from, to, tokenId] STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] // update approval 0x00 dup4 // [tokenId, address(0), from, to, tokenId] - [SINGLE_APPROVAL_LOCATION] + [SINGLE_APPROVAL_LOCATION] // [approval_slot, tokenId, address(0), from, to, tokenId] STORE_ELEMENT_FROM_KEYS(0x00) // [from, to, tokenId] } From 2062314400c7787f4cee3762f290597fe03c3543 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Sep 2022 23:20:33 -0400 Subject: [PATCH 21/27] safe transfer from with data copying --- src/tokens/ERC721.huff | 96 ++++++++++++++++++++++++++- test/tokens/ERC721.t.sol | 25 ++++++- test/tokens/mocks/ERC721Wrappers.huff | 7 +- 3 files changed, 122 insertions(+), 6 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 5e03c4f2..add459b2 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -20,6 +20,7 @@ #define function transfer(address,uint256) nonpayable returns () #define function transferFrom(address,address,uint256) nonpayable returns () #define function safeTransferFrom(address,address,uint256) nonpayable returns () +#define function safeTransferFrom(address,address,uint256,bytes) nonpayable returns () #define function approve(address,uint256) nonpayable returns () #define function setApprovalForAll(address,bool) nonpayable returns () @@ -342,7 +343,7 @@ 0x64 mstore // [onERC721Received, to] // Blank bytes array as 4th arg (no data) - 0x80 0x84 mstore + 0x80 0x84 mstore 0x00 0xA4 mstore // Clean scratch space for return data use @@ -375,7 +376,98 @@ } #define macro SAFE_TRANSFER_FROM_WITH_DATA() = takes (0) returns (0) { - 0x00 0x00 revert + // Setup the stack for the transfer function. + 0x44 calldataload // [tokenId] + 0x24 calldataload // [to, tokenId] + 0x04 calldataload // [from, to, tokenId] + + TRANSFER_TAKE_FROM() // [from, to, tokenId] + TRANSFER_GIVE_TO() // [from, to, tokenId] + + // Emit the transfer event. + __EVENT_HASH(Transfer) // [sig, from, to, tokenId] + 0x00 0x00 // [0, 0, sig, from, to, tokenId] + log4 // [] + + // Make sure we can transfer to the recipient + 0x24 calldataload // [to] + dup1 extcodesize // [to.code.length, to] + iszero safe jumpi // [to] + + // onERC721Received Selector + // 0xf0b9e5ba + 0x150b7a02 dup1 // [onERC721Received, onERC721Received, to] + 0xE0 shl // [onERC721Received_shifted, onERC721Received, to] + + // Store the left-shifted selector for call + 0x20 mstore // [onERC721Received, to] + + // Store the msg.sender as the first arg + caller 0x24 mstore // [onERC721Received, to] + + // Store from as the second arg + 0x04 calldataload // [from, onERC721Received, to] + 0x44 mstore // [onERC721Received, to] + + // Id is the third arg + 0x44 calldataload // [tokenId, onERC721Received, to] + 0x64 mstore // [onERC721Received, to] + + // Blank bytes array as 4th arg (no data) + 0x64 calldataload // [len(bytes), onERC721Received, to] + dup1 // [len(bytes), len(bytes), onERC721Received, to] + + // Store the length of the data + 0x84 mstore // [len(bytes), onERC721Received, to] + + // Prepare the loop + 0xA4 // [mem_i, len(bytes), onERC721Received, to] + 0x00 // [i, mem_i, len(bytes), onERC721Received, to] + + loop: + 0x20 add // [i + 0x20, mem_i, len(bytes), onERC721Received, to] + swap1 0x20 add // [mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] + dup2 // [i + 0x20, mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] + 0x84 add // [i + 0x20 + 0x84, mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] + calldataload // [bytes[i], mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] + dup2 // [mem_i + 0x20, bytes[i], mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] + + mstore // [mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] + swap1 // [i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] + dup3 dup2 // [i + 0x20, len(bytes), i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] + lt loop jumpi // [i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] + + // Clean up stack + pop pop // [len(bytes), onERC721Received, to] + + // Clean scratch space for return data use + 0x00 dup1 mstore // [len(bytes), onERC721Received, to] + + // Call address(to).onERC721Received(msg.sender, from, tokenId, bytes) + 0x20 // [retSize, len(bytes), onERC721Received, to] + 0x00 // [retOffset, retSize, len(bytes), onERC721Received, to] + 0xA4 // [0xA4, retOffset, retSize, len(bytes), onERC721Received, to] + dup4 add // [argSize, retOffset, retSize, len(bytes), onERC721Received, to] + dup3 // [argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to] + dup3 // [value, argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to] + dup7 // [to, value, argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to] + gas // [gas, to, value, argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to] + call // [success, onERC721Received, to] + + // Revert if call isn't successful + cont jumpi // [onERC721Received, to] + 0x00 dup1 revert + cont: + + // Compare the return data to the onERC721Received selector + 0x00 mload 0xE0 shr // [response, onERC721Received, to] + eq safe jumpi // [to] + + // Revert if the return data is not accepted + UNSAFE_RECIPIENT(0x00) + + safe: + stop } /// >>>>>>>>>>>>>>>>>>>>> INTERNAL HELPERS <<<<<<<<<<<<<<<<<<<<<< /// diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index c6766f2f..bfc96a91 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -38,6 +38,7 @@ interface IERC721 { function transfer(address, uint256) external; function transferFrom(address, address, uint256) external; function safeTransferFrom(address, address, uint256) external; + function safeTransferFrom(address, address, uint256, bytes calldata) external; function approve(address, uint256) external; function setApprovalForAll(address, bool) external; @@ -229,7 +230,6 @@ contract ERC721Test is Test { vm.assume(from != address(0)); ERC721Recipient recipient = new ERC721Recipient(); - console2.logBytes4(ERC721Recipient.onERC721Received.selector); token.mint(from, 1337); @@ -248,4 +248,27 @@ contract ERC721Test is Test { assertEq(recipient.id(), 1337); assertEq(keccak256(abi.encode(recipient.data())), keccak256(abi.encode(""))); } + + function testSafeTransferFromToERC721RecipientWithData(address from) public { + vm.assume(from != address(0)); + + ERC721Recipient recipient = new ERC721Recipient(); + + token.mint(from, 1337); + + vm.prank(from); + token.setApprovalForAll(address(this), true); + + token.safeTransferFrom(from, address(recipient), 1337, "testing 123"); + + assertEq(token.getApproved(1337), address(0)); + assertEq(token.ownerOf(1337), address(recipient)); + assertEq(token.balanceOf(address(recipient)), 1); + assertEq(token.balanceOf(from), 0); + + assertEq(recipient.operator(), address(this)); + assertEq(recipient.from(), from); + assertEq(recipient.id(), 1337); + assertEq(keccak256(abi.encode(recipient.data())), keccak256(abi.encode("testing 123"))); + } } diff --git a/test/tokens/mocks/ERC721Wrappers.huff b/test/tokens/mocks/ERC721Wrappers.huff index 69f63418..1621f048 100644 --- a/test/tokens/mocks/ERC721Wrappers.huff +++ b/test/tokens/mocks/ERC721Wrappers.huff @@ -26,7 +26,6 @@ dup1 __FUNC_SIG(transferFrom) eq transferFrom jumpi dup1 __FUNC_SIG(safeTransferFrom) eq safeTransferFrom jumpi - dup1 __FUNC_SIG(safeTransferFromWithData) eq safeTransferFromWithData jumpi dup1 __FUNC_SIG(name) eq name jumpi dup1 __FUNC_SIG(symbol) eq symbol jumpi @@ -39,6 +38,8 @@ dup1 __FUNC_SIG(balanceOf) eq balanceOf jumpi dup1 __FUNC_SIG(ownerOf) eq ownerOf jumpi + dup1 __FUNC_SIG("safeTransferFrom(address,address,uint256,bytes)") eq safeTransferFromData jumpi + // Revert on failed dispatch 0x00 dup1 revert @@ -56,8 +57,8 @@ TRANSFER_FROM() safeTransferFrom: SAFE_TRANSFER_FROM() - safeTransferFromWithData: - SAFE_TRANSFER_FROM_WITH_DATA() // not implemented yet + safeTransferFromData: + SAFE_TRANSFER_FROM_WITH_DATA() name: NAME() From 15f5824c6850a548d032ab235f1aac9a866a96b1 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Sep 2022 23:22:00 -0400 Subject: [PATCH 22/27] pop the bytes len --- src/tokens/ERC721.huff | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index add459b2..cb9aa1ea 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -452,13 +452,15 @@ dup3 // [value, argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to] dup7 // [to, value, argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to] gas // [gas, to, value, argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to] - call // [success, onERC721Received, to] + call // [success, len(bytes), onERC721Received, to] // Revert if call isn't successful - cont jumpi // [onERC721Received, to] + cont jumpi // [len(bytes), onERC721Received, to] 0x00 dup1 revert cont: + pop // [onERC721Received, to] + // Compare the return data to the onERC721Received selector 0x00 mload 0xE0 shr // [response, onERC721Received, to] eq safe jumpi // [to] From 023a47b0d0006af018dc77f7bd8bf985db52a7b1 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Sep 2022 23:42:50 -0400 Subject: [PATCH 23/27] try and fix loop --- README.md | 2 +- src/tokens/ERC721.huff | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5ed21775..e4d0e840 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ math ├─ Trigonometry — "Basic trigonometry functions where inputs and outputs are integers" tokens ├─ ERC20 — "Modern and gas efficient ERC20 + EIP-2612 implementation" -├─ ERC721 — TODO — "Modern, minimalist, and gas efficient ERC721 implementation" +├─ ERC721 — "Modern, minimalist, and gas efficient ERC721 implementation" ├─ ERC1155 — "Minimalist and gas efficient standard ERC1155 implementation" ├─ ERC4626 — TODO - "Minimal ERC4626 tokenized Vault implementation" utils diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index cb9aa1ea..efccf77b 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -421,20 +421,22 @@ 0x84 mstore // [len(bytes), onERC721Received, to] // Prepare the loop + 0x84 add // [len(bytes) + 0x84, onERC721Received, to] 0xA4 // [mem_i, len(bytes), onERC721Received, to] - 0x00 // [i, mem_i, len(bytes), onERC721Received, to] + 0x84 // [i, mem_i, len(bytes), onERC721Received, to] loop: + dup1 // [i, i, mem_i, len(bytes), onERC721Received, to] + calldataload // [bytes[i], i, mem_i, len(bytes), onERC721Received, to] + dup3 // [mem_i, bytes[i], i, mem_i, len(bytes), onERC721Received, to] + + mstore // [i, mem_i, len(bytes), onERC721Received, to] 0x20 add // [i + 0x20, mem_i, len(bytes), onERC721Received, to] swap1 0x20 add // [mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] - dup2 // [i + 0x20, mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] - 0x84 add // [i + 0x20 + 0x84, mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] - calldataload // [bytes[i], mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] - dup2 // [mem_i + 0x20, bytes[i], mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] - - mstore // [mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] swap1 // [i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] - dup3 dup2 // [i + 0x20, len(bytes), i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] + + dup3 0x20 add // [len(bytes) + 0x20, i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] + dup2 // [i + 0x20, len(bytes) + 0x20, i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] lt loop jumpi // [i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] // Clean up stack From a651959ebee2c321f7aee30baad0a6c34266abdb Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Sep 2022 23:45:05 -0400 Subject: [PATCH 24/27] loop fixes --- src/tokens/ERC721.huff | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index efccf77b..1ab7567f 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -440,7 +440,8 @@ lt loop jumpi // [i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] // Clean up stack - pop pop // [len(bytes), onERC721Received, to] + pop pop pop // [onERC721Received, to] + 0x64 calldataload // [len(bytes), onERC721Received, to] // Clean scratch space for return data use 0x00 dup1 mstore // [len(bytes), onERC721Received, to] From d0047c91c69009f324ca9078ef52959ea82b9071 Mon Sep 17 00:00:00 2001 From: clabby Date: Wed, 14 Sep 2022 00:19:35 -0400 Subject: [PATCH 25/27] `calldatacopy` instead of loop --- src/tokens/ERC721.huff | 42 +++++++++--------------------------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 1ab7567f..3c657111 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -413,35 +413,13 @@ 0x44 calldataload // [tokenId, onERC721Received, to] 0x64 mstore // [onERC721Received, to] - // Blank bytes array as 4th arg (no data) - 0x64 calldataload // [len(bytes), onERC721Received, to] - dup1 // [len(bytes), len(bytes), onERC721Received, to] - - // Store the length of the data - 0x84 mstore // [len(bytes), onERC721Received, to] - - // Prepare the loop - 0x84 add // [len(bytes) + 0x84, onERC721Received, to] - 0xA4 // [mem_i, len(bytes), onERC721Received, to] - 0x84 // [i, mem_i, len(bytes), onERC721Received, to] - - loop: - dup1 // [i, i, mem_i, len(bytes), onERC721Received, to] - calldataload // [bytes[i], i, mem_i, len(bytes), onERC721Received, to] - dup3 // [mem_i, bytes[i], i, mem_i, len(bytes), onERC721Received, to] - - mstore // [i, mem_i, len(bytes), onERC721Received, to] - 0x20 add // [i + 0x20, mem_i, len(bytes), onERC721Received, to] - swap1 0x20 add // [mem_i + 0x20, i + 0x20, len(bytes), onERC721Received, to] - swap1 // [i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] - - dup3 0x20 add // [len(bytes) + 0x20, i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] - dup2 // [i + 0x20, len(bytes) + 0x20, i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] - lt loop jumpi // [i + 0x20, mem_i + 0x20, len(bytes), onERC721Received, to] - - // Clean up stack - pop pop pop // [onERC721Received, to] - 0x64 calldataload // [len(bytes), onERC721Received, to] + 0x84 calldataload // [len(data), onERC721Received, to] + 0x05 shl // [len(data) * 0x20, onERC721Received, to] + 0x40 add // [len(data) * 0x20 + 0x40, onERC721Received, to] + dup1 // [len(data) * 0x20 + 0x40, len(data) * 0x20 + 0x40, onERC721received, to] + 0x64 // [0x64, len(data) * 0x20 + 0x40, len(data) * 0x20 + 0x40, onERC721received, to] + 0x84 // [0x20, 0x64, len(data) * 0x20 + 0x40, len(data) * 0x20 + 0x40, onERC721received, to] + calldatacopy // [len(bytes), onERC721received, to] // Clean scratch space for return data use 0x00 dup1 mstore // [len(bytes), onERC721Received, to] @@ -449,8 +427,8 @@ // Call address(to).onERC721Received(msg.sender, from, tokenId, bytes) 0x20 // [retSize, len(bytes), onERC721Received, to] 0x00 // [retOffset, retSize, len(bytes), onERC721Received, to] - 0xA4 // [0xA4, retOffset, retSize, len(bytes), onERC721Received, to] - dup4 add // [argSize, retOffset, retSize, len(bytes), onERC721Received, to] + swap1 swap2 // [len(bytes), retOffset, retSize, onERC721Received, to] + 0x64 add // [argSize, retOffset, retSize, onERC721Received, to] dup3 // [argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to] dup3 // [value, argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to] dup7 // [to, value, argOffset, argSize, retOffset, retSize, len(bytes), onERC721Received, to] @@ -462,8 +440,6 @@ 0x00 dup1 revert cont: - pop // [onERC721Received, to] - // Compare the return data to the onERC721Received selector 0x00 mload 0xE0 shr // [response, onERC721Received, to] eq safe jumpi // [to] From 5e660849d48164720ba30f25073b8025177f8b2c Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Wed, 14 Sep 2022 00:44:03 -0400 Subject: [PATCH 26/27] final upgrades --- src/tokens/ERC1155.huff | 4 ++-- src/tokens/ERC721.huff | 16 ++++++---------- test/tokens/ERC1155.t.sol | 6 +++--- test/tokens/ERC721.t.sol | 2 ++ test/tokens/mocks/ERC721Wrappers.huff | 2 +- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/tokens/ERC1155.huff b/src/tokens/ERC1155.huff index 32ba99fd..57e62b8a 100644 --- a/src/tokens/ERC1155.huff +++ b/src/tokens/ERC1155.huff @@ -541,8 +541,8 @@ 0x80 // [&ret] // Get lengths of dynamic arrays, duplicate in prepare to compare - 0x24 calldataload 0x4 add dup1 // [&ids, &ids, &owners, &owners, &ret] - 0x04 calldataload 0x4 add dup1 // [&ids, &ids, &ret] + 0x24 calldataload 0x4 add dup1 // [&ids, &ids, &ret] + 0x04 calldataload 0x4 add dup1 // [&ids, &ids, &owners, &owners, &ret] // compare lengths swap2 // [&owners, &ids, &ids, &owners, &ret] diff --git a/src/tokens/ERC721.huff b/src/tokens/ERC721.huff index 3c657111..0860cf69 100644 --- a/src/tokens/ERC721.huff +++ b/src/tokens/ERC721.huff @@ -114,9 +114,11 @@ 0x20 0x00 return // [] } - +/// @notice Token URI #define macro TOKEN_URI() = takes (0) returns (0) { - 0x00 0x00 revert + 0x20 0x00 mstore + 0x00 0x20 mstore + 0x40 0x00 return } #define macro SUPPORTS_INTERFACE() = takes (0) returns (0) { @@ -346,9 +348,6 @@ 0x80 0x84 mstore 0x00 0xA4 mstore - // Clean scratch space for return data use - 0x00 dup1 mstore // [onERC721Received, to] - // Call address(to).onERC721Received(msg.sender, from, tokenId, "") 0x20 // [retSize, onERC721Received, to] 0x00 // [retOffset, retSize, onERC721Received, to] @@ -418,11 +417,8 @@ 0x40 add // [len(data) * 0x20 + 0x40, onERC721Received, to] dup1 // [len(data) * 0x20 + 0x40, len(data) * 0x20 + 0x40, onERC721received, to] 0x64 // [0x64, len(data) * 0x20 + 0x40, len(data) * 0x20 + 0x40, onERC721received, to] - 0x84 // [0x20, 0x64, len(data) * 0x20 + 0x40, len(data) * 0x20 + 0x40, onERC721received, to] - calldatacopy // [len(bytes), onERC721received, to] - - // Clean scratch space for return data use - 0x00 dup1 mstore // [len(bytes), onERC721Received, to] + 0x84 // [0x20, 0x64, len(data) * 0x20 + 0x40, len(data) * 0x20 + 0x40, onERC721received, to] + calldatacopy // [len(bytes), onERC721received, to] // Call address(to).onERC721Received(msg.sender, from, tokenId, bytes) 0x20 // [retSize, len(bytes), onERC721Received, to] diff --git a/test/tokens/ERC1155.t.sol b/test/tokens/ERC1155.t.sol index 93f86ccb..0d148948 100644 --- a/test/tokens/ERC1155.t.sol +++ b/test/tokens/ERC1155.t.sol @@ -1225,8 +1225,8 @@ contract ERC1155Test is Test, ERC1155Recipient, FuzzingUtils{ uint256 minLength = min3(tos.length, ids.length, amounts.length); for (uint256 i = 0; i < minLength; i++) { - vm.assume(tos[i] != address(0)); - vm.assume(tos[i] != address(this)); + tos[i] = tos[i] == address(0) ? address(0xBEEF) : tos[i]; + tos[i] = tos[i] == address(this) ? address(0xBEEF) : tos[i]; } address[] memory normalizedTos = new address[](minLength); @@ -1234,7 +1234,7 @@ contract ERC1155Test is Test, ERC1155Recipient, FuzzingUtils{ for (uint256 i = 0; i < minLength; i++) { uint256 id = ids[i]; - address to = tos[i] == address(0) ? address(0xBEEF) : tos[i]; + address to = tos[i]; uint256 remainingMintAmountForId = type(uint256).max - userMintAmounts[to][id]; diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index bfc96a91..ce5c88ba 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -63,6 +63,7 @@ contract ERC721Test is Test { function testMetadata() public { assertEq(keccak256(abi.encode(token.name())), keccak256(abi.encode("Token"))); assertEq(keccak256(abi.encode(token.symbol())), keccak256(abi.encode("TKN"))); + assertEq(keccak256(abi.encode(token.tokenURI(1))), keccak256(abi.encode(""))); } function testMint() public { @@ -253,6 +254,7 @@ contract ERC721Test is Test { vm.assume(from != address(0)); ERC721Recipient recipient = new ERC721Recipient(); + vm.assume(from != address(recipient)); token.mint(from, 1337); diff --git a/test/tokens/mocks/ERC721Wrappers.huff b/test/tokens/mocks/ERC721Wrappers.huff index 1621f048..61c43d99 100644 --- a/test/tokens/mocks/ERC721Wrappers.huff +++ b/test/tokens/mocks/ERC721Wrappers.huff @@ -65,7 +65,7 @@ symbol: SYMBOL() tokenURI: - TOKEN_URI() // not implemented yet + TOKEN_URI() supportsInterface: SUPPORTS_INTERFACE() From 52f185468b21cc23c4b22f4882022ceb1a6c9c57 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Wed, 14 Sep 2022 00:46:45 -0400 Subject: [PATCH 27/27] assume not beef --- test/tokens/ERC721.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/test/tokens/ERC721.t.sol b/test/tokens/ERC721.t.sol index ce5c88ba..19e178db 100644 --- a/test/tokens/ERC721.t.sol +++ b/test/tokens/ERC721.t.sol @@ -193,6 +193,7 @@ contract ERC721Test is Test { function testTransferFromApproveAll(address from) public { vm.assume(from != address(0)); + vm.assume(from != address(0xBEEF)); // Mint a token token.mint(from, 1337);