diff --git a/contracts/src/ERC721.sol b/contracts/src/ERC721.sol index c7e89c2655..0ad6278a0c 100644 --- a/contracts/src/ERC721.sol +++ b/contracts/src/ERC721.sol @@ -51,6 +51,9 @@ contract ERC721 is IERC721 { bool approved ); + // Keeps track of current token supply + uint256 internal _totalSupply = 0; + // Mapping from token ID to owner address mapping(uint => address) internal _ownerOf; @@ -79,6 +82,10 @@ contract ERC721 is IERC721 { return _balanceOf[owner]; } + function totalSupply() external view returns (uint256) { + return _totalSupply; + } + function setApprovalForAll(address operator, bool approved) external { isApprovedForAll[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); @@ -158,6 +165,7 @@ contract ERC721 is IERC721 { require(_ownerOf[id] == address(0), "already minted"); _balanceOf[to]++; + _totalSupply++; _ownerOf[id] = to; emit Transfer(address(0), to, id); @@ -168,6 +176,7 @@ contract ERC721 is IERC721 { require(owner != address(0), "not minted"); _balanceOf[owner] -= 1; + _totalSupply--; delete _ownerOf[id]; delete _approvals[id]; @@ -222,4 +231,9 @@ contract MyNFT is ERC721 { require(msg.sender == _ownerOf[id], "not owner"); _burn(id); } + + function royaltyInfo(uint, uint256 salePrice) external pure returns (address receiver, uint256 royaltyAmount) { + receiver = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; + royaltyAmount = (salePrice * 500) / 10_000; + } } diff --git a/contracts/test/CW721toERC721PointerTest.js b/contracts/test/CW721toERC721PointerTest.js index d0e0c36d00..98a27e5c31 100644 --- a/contracts/test/CW721toERC721PointerTest.js +++ b/contracts/test/CW721toERC721PointerTest.js @@ -73,39 +73,48 @@ describe("CW721 to ERC721 Pointer", function () { }); }); + it("should retrieve number of circulating tokens", async function () { + const result = await queryWasm(pointer, "num_tokens", {}); + expect(result).to.deep.equal({data:{count:3}}); + }); + it("should retrieve contract information", async function () { const result = await queryWasm(pointer, "contract_info", {}); expect(result).to.deep.equal({data:{name:"MyNFT",symbol:"MYNFT"}}); }); - it("should fetch NFT info based on token ID", async function () { - const result = await queryWasm(pointer, "nft_info", { token_id: "1" }); - expect(result).to.deep.equal({ data: { token_uri: 'https://sei.io/token/1', extension: '' } }); - }); - it("should fetch all information about an NFT", async function () { const result = await queryWasm(pointer, "all_nft_info", { token_id: "1" }); - expect(result).to.deep.equal({ - data: { - access: { - owner: accounts[0].seiAddress, - approvals: [ - { - spender: accounts[1].seiAddress, - expires: { - never: {} - } - } - ] - }, - info: { - token_uri: "https://sei.io/token/1", - extension: "" + expect(result.data.access).to.deep.equal({ + owner: accounts[0].seiAddress, + approvals: [ + { + spender: accounts[1].seiAddress, + expires: { + never: {} + } } - } + ] }); + expect(result.data.info.token_uri).to.equal('https://sei.io/token/1'); + expect(result.data.info.extension.royalty_percentage).to.equal(5); + expect(result.data.info.extension.royalty_payment_address).to.include("sei1"); + }); + + it("should retrieve all minted NFT token ids", async function () { + const result = await queryWasm(pointer, "all_tokens", {}); + expect(result).to.deep.equal({data:{tokens:["1","2","3"]}}); }); + it("should retrieve list of 1 minted NFT token id after token id 1", async function () { + const result = await queryWasm(pointer, "all_tokens", { start_after: "1", limit: 1 }); + expect(result).to.deep.equal({data:{tokens:["2"]}}); + }); + + it("should retrieve list of NFT token ids owned by admin", async function () { + const result = await queryWasm(pointer, "tokens", { owner: admin.seiAddress }); + expect(result).to.deep.equal({data:{tokens:["3"]}}); + }); }) describe("execute operations", function () { diff --git a/contracts/test/lib.js b/contracts/test/lib.js index 86f59dc2c9..2b078817eb 100644 --- a/contracts/test/lib.js +++ b/contracts/test/lib.js @@ -23,6 +23,7 @@ const ABI = { "function symbol() view returns (string)", "function totalSupply() view returns (uint256)", "function tokenURI(uint256 tokenId) view returns (string)", + "function royaltyInfo(uint256 tokenId, uint256 salePrice) view returns (address, uint256)", "function balanceOf(address owner) view returns (uint256 balance)", "function ownerOf(uint256 tokenId) view returns (address owner)", "function getApproved(uint256 tokenId) view returns (address operator)", diff --git a/contracts/wasm/cwerc721.wasm b/contracts/wasm/cwerc721.wasm index 318ada77bc..a578175b00 100644 Binary files a/contracts/wasm/cwerc721.wasm and b/contracts/wasm/cwerc721.wasm differ diff --git a/example/contracts/erc721/DummyERC721.abi b/example/contracts/erc721/DummyERC721.abi index 9395a3fc51..ce4fea57ee 100644 --- a/example/contracts/erc721/DummyERC721.abi +++ b/example/contracts/erc721/DummyERC721.abi @@ -1 +1 @@ -[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"operator","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"owner","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"randomAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"operator","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"owner","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"randomAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"salePrice","type":"uint256"}],"name":"royaltyInfo","outputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"royaltyAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"supply","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/example/contracts/erc721/DummyERC721.bin b/example/contracts/erc721/DummyERC721.bin index faddd7a16f..2eb470db04 100644 --- a/example/contracts/erc721/DummyERC721.bin +++ b/example/contracts/erc721/DummyERC721.bin @@ -1 +1 @@ -608060405273f39fd6e51aad88f6f4ce6ab8827279cfffb9226660045f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550348015610063575f80fd5b5061091f806100715f395ff3fe608060405234801561000f575f80fd5b50600436106100e8575f3560e01c806370a082311161008a578063b88d4fde11610064578063b88d4fde14610258578063c87b56dd14610274578063d5bee9f5146102a4578063e985e9c5146102c2576100e8565b806370a08231146101ee57806395d89b411461021e578063a22cb4651461023c576100e8565b8063095ea7b3116100c6578063095ea7b31461016a57806323b872dd1461018657806342842e0e146101a25780636352211e146101be576100e8565b806301ffc9a7146100ec57806306fdde031461011c578063081812fc1461013a575b5f80fd5b61010660048036038101906101019190610495565b6102f2565b60405161011391906104da565b60405180910390f35b6101246102fc565b604051610131919061057d565b60405180910390f35b610154600480360381019061014f91906105d0565b610339565b604051610161919061063a565b60405180910390f35b610184600480360381019061017f919061067d565b61033f565b005b6101a0600480360381019061019b91906106bb565b610343565b005b6101bc60048036038101906101b791906106bb565b610348565b005b6101d860048036038101906101d391906105d0565b61034d565b6040516101e5919061063a565b60405180910390f35b6102086004803603810190610203919061070b565b610377565b6040516102159190610745565b60405180910390f35b610226610381565b604051610233919061057d565b60405180910390f35b61025660048036038101906102519190610788565b6103be565b005b610272600480360381019061026d9190610827565b6103c2565b005b61028e600480360381019061028991906105d0565b6103c9565b60405161029b919061057d565b60405180910390f35b6102ac610408565b6040516102b9919061063a565b60405180910390f35b6102dc60048036038101906102d791906108ab565b61042d565b6040516102e991906104da565b60405180910390f35b5f60019050919050565b60606040518060400160405280600b81526020017f44756d6d79455243373231000000000000000000000000000000000000000000815250905090565b5f919050565b5050565b505050565b505050565b5f60045f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f60329050919050565b60606040518060400160405280600581526020017f44554d4d59000000000000000000000000000000000000000000000000000000815250905090565b5050565b5050505050565b60606040518060400160405280601381526020017f68747470733a2f2f6578616d706c652e636f6d000000000000000000000000008152509050919050565b60045f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f6001905092915050565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61047481610440565b811461047e575f80fd5b50565b5f8135905061048f8161046b565b92915050565b5f602082840312156104aa576104a9610438565b5b5f6104b784828501610481565b91505092915050565b5f8115159050919050565b6104d4816104c0565b82525050565b5f6020820190506104ed5f8301846104cb565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b8381101561052a57808201518184015260208101905061050f565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61054f826104f3565b61055981856104fd565b935061056981856020860161050d565b61057281610535565b840191505092915050565b5f6020820190508181035f8301526105958184610545565b905092915050565b5f819050919050565b6105af8161059d565b81146105b9575f80fd5b50565b5f813590506105ca816105a6565b92915050565b5f602082840312156105e5576105e4610438565b5b5f6105f2848285016105bc565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610624826105fb565b9050919050565b6106348161061a565b82525050565b5f60208201905061064d5f83018461062b565b92915050565b61065c8161061a565b8114610666575f80fd5b50565b5f8135905061067781610653565b92915050565b5f806040838503121561069357610692610438565b5b5f6106a085828601610669565b92505060206106b1858286016105bc565b9150509250929050565b5f805f606084860312156106d2576106d1610438565b5b5f6106df86828701610669565b93505060206106f086828701610669565b9250506040610701868287016105bc565b9150509250925092565b5f602082840312156107205761071f610438565b5b5f61072d84828501610669565b91505092915050565b61073f8161059d565b82525050565b5f6020820190506107585f830184610736565b92915050565b610767816104c0565b8114610771575f80fd5b50565b5f813590506107828161075e565b92915050565b5f806040838503121561079e5761079d610438565b5b5f6107ab85828601610669565b92505060206107bc85828601610774565b9150509250929050565b5f80fd5b5f80fd5b5f80fd5b5f8083601f8401126107e7576107e66107c6565b5b8235905067ffffffffffffffff811115610804576108036107ca565b5b6020830191508360018202830111156108205761081f6107ce565b5b9250929050565b5f805f805f608086880312156108405761083f610438565b5b5f61084d88828901610669565b955050602061085e88828901610669565b945050604061086f888289016105bc565b935050606086013567ffffffffffffffff8111156108905761088f61043c565b5b61089c888289016107d2565b92509250509295509295909350565b5f80604083850312156108c1576108c0610438565b5b5f6108ce85828601610669565b92505060206108df85828601610669565b915050925092905056fea2646970667358221220cac0547d8cdc2d72600e74bf9296eee8caaa1ec4c78594f3b8947dd3ec2baef364736f6c63430008140033 \ No newline at end of file +608060405273f39fd6e51aad88f6f4ce6ab8827279cfffb9226660045f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503480156062575f80fd5b50610aec806100705f395ff3fe608060405234801561000f575f80fd5b50600436106100fe575f3560e01c80636352211e11610095578063b88d4fde11610064578063b88d4fde146102bd578063c87b56dd146102d9578063d5bee9f514610309578063e985e9c514610327576100fe565b80636352211e1461022357806370a082311461025357806395d89b4114610283578063a22cb465146102a1576100fe565b806318160ddd116100d157806318160ddd1461019c57806323b872dd146101ba5780632a55205a146101d657806342842e0e14610207576100fe565b806301ffc9a71461010257806306fdde0314610132578063081812fc14610150578063095ea7b314610180575b5f80fd5b61011c6004803603810190610117919061054c565b610357565b6040516101299190610591565b60405180910390f35b61013a610361565b604051610147919061061a565b60405180910390f35b61016a6004803603810190610165919061066d565b61039e565b60405161017791906106d7565b60405180910390f35b61019a6004803603810190610195919061071a565b6103a4565b005b6101a46103a8565b6040516101b19190610767565b60405180910390f35b6101d460048036038101906101cf9190610780565b6103b0565b005b6101f060048036038101906101eb91906107d0565b6103b5565b6040516101fe92919061080e565b60405180910390f35b610221600480360381019061021c9190610780565b6103ff565b005b61023d6004803603810190610238919061066d565b610404565b60405161024a91906106d7565b60405180910390f35b61026d60048036038101906102689190610835565b61042e565b60405161027a9190610767565b60405180910390f35b61028b610438565b604051610298919061061a565b60405180910390f35b6102bb60048036038101906102b6919061088a565b610475565b005b6102d760048036038101906102d29190610929565b610479565b005b6102f360048036038101906102ee919061066d565b610480565b604051610300919061061a565b60405180910390f35b6103116104bf565b60405161031e91906106d7565b60405180910390f35b610341600480360381019061033c91906109ad565b6104e4565b60405161034e9190610591565b60405180910390f35b5f60019050919050565b60606040518060400160405280600b81526020017f44756d6d79455243373231000000000000000000000000000000000000000000815250905090565b5f919050565b5050565b5f6065905090565b505050565b5f8060045f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1691506127106101f4846103ec9190610a18565b6103f69190610a86565b90509250929050565b505050565b5f60045f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f60329050919050565b60606040518060400160405280600581526020017f44554d4d59000000000000000000000000000000000000000000000000000000815250905090565b5050565b5050505050565b60606040518060400160405280601381526020017f68747470733a2f2f6578616d706c652e636f6d000000000000000000000000008152509050919050565b60045f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f6001905092915050565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61052b816104f7565b8114610535575f80fd5b50565b5f8135905061054681610522565b92915050565b5f60208284031215610561576105606104ef565b5b5f61056e84828501610538565b91505092915050565b5f8115159050919050565b61058b81610577565b82525050565b5f6020820190506105a45f830184610582565b92915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6105ec826105aa565b6105f681856105b4565b93506106068185602086016105c4565b61060f816105d2565b840191505092915050565b5f6020820190508181035f83015261063281846105e2565b905092915050565b5f819050919050565b61064c8161063a565b8114610656575f80fd5b50565b5f8135905061066781610643565b92915050565b5f60208284031215610682576106816104ef565b5b5f61068f84828501610659565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6106c182610698565b9050919050565b6106d1816106b7565b82525050565b5f6020820190506106ea5f8301846106c8565b92915050565b6106f9816106b7565b8114610703575f80fd5b50565b5f81359050610714816106f0565b92915050565b5f80604083850312156107305761072f6104ef565b5b5f61073d85828601610706565b925050602061074e85828601610659565b9150509250929050565b6107618161063a565b82525050565b5f60208201905061077a5f830184610758565b92915050565b5f805f60608486031215610797576107966104ef565b5b5f6107a486828701610706565b93505060206107b586828701610706565b92505060406107c686828701610659565b9150509250925092565b5f80604083850312156107e6576107e56104ef565b5b5f6107f385828601610659565b925050602061080485828601610659565b9150509250929050565b5f6040820190506108215f8301856106c8565b61082e6020830184610758565b9392505050565b5f6020828403121561084a576108496104ef565b5b5f61085784828501610706565b91505092915050565b61086981610577565b8114610873575f80fd5b50565b5f8135905061088481610860565b92915050565b5f80604083850312156108a05761089f6104ef565b5b5f6108ad85828601610706565b92505060206108be85828601610876565b9150509250929050565b5f80fd5b5f80fd5b5f80fd5b5f8083601f8401126108e9576108e86108c8565b5b8235905067ffffffffffffffff811115610906576109056108cc565b5b602083019150836001820283011115610922576109216108d0565b5b9250929050565b5f805f805f60808688031215610942576109416104ef565b5b5f61094f88828901610706565b955050602061096088828901610706565b945050604061097188828901610659565b935050606086013567ffffffffffffffff811115610992576109916104f3565b5b61099e888289016108d4565b92509250509295509295909350565b5f80604083850312156109c3576109c26104ef565b5b5f6109d085828601610706565b92505060206109e185828601610706565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610a228261063a565b9150610a2d8361063a565b9250828202610a3b8161063a565b91508282048414831517610a5257610a516109eb565b5b5092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f610a908261063a565b9150610a9b8361063a565b925082610aab57610aaa610a59565b5b82820490509291505056fea2646970667358221220044a679d63c6ba5d9d54e5c3a3a74e6dcad05fbb5758067ef15fc25af3a3df3e64736f6c63430008190033 \ No newline at end of file diff --git a/example/contracts/erc721/ERC721.sol b/example/contracts/erc721/ERC721.sol index 93044d8acc..b4d844a6f8 100644 --- a/example/contracts/erc721/ERC721.sol +++ b/example/contracts/erc721/ERC721.sol @@ -57,6 +57,10 @@ contract DummyERC721 is IERC721 { return 50; } + function totalSupply() public view returns (uint256 supply) { + supply = 101; + } + function ownerOf(uint256 tokenId) public view override returns (address owner) { return randomAddress; } @@ -65,6 +69,11 @@ contract DummyERC721 is IERC721 { return "https://example.com"; } + function royaltyInfo(uint256 tokenId, uint256 salePrice) external view returns (address receiver, uint256 royaltyAmount) { + receiver = randomAddress; + royaltyAmount = (salePrice * 500) / 10_000; + } + function transferFrom(address from, address to, uint256 tokenId) public override {} function safeTransferFrom(address from, address to, uint256 tokenId) public override {} diff --git a/example/cosmwasm/cw721/Cargo.lock b/example/cosmwasm/cw721/Cargo.lock index 57817b7bcf..7d070bc7d7 100644 --- a/example/cosmwasm/cw721/Cargo.lock +++ b/example/cosmwasm/cw721/Cargo.lock @@ -57,9 +57,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" +checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" [[package]] name = "byteorder" @@ -81,9 +81,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cosmwasm-crypto" -version = "1.5.2" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6aa9f904de106fa16443ad14ec2abe75e94ba003bb61c681c0e43d4c58d2a" +checksum = "dd50718a2b6830ce9eb5d465de5a018a12e71729d66b70807ce97e6dd14f931d" dependencies = [ "digest 0.10.7", "ecdsa", @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.5.2" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40abec852f3d4abec6d44ead9a58b78325021a1ead1e7229c3471414e57b2e49" +checksum = "242e98e7a231c122e08f300d9db3262d1007b51758a8732cd6210b3e9faa4f3a" dependencies = [ "syn 1.0.109", ] @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.5.2" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad011ae7447188e26e4a7dbca2fcd0fc186aa21ae5c86df0503ea44c78f9e469" +checksum = "78c1556156fdf892a55cced6115968b961eaaadd6f724a2c2cb7d1e168e32dd3" dependencies = [ "base64", "bech32", @@ -577,9 +577,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "k256" -version = "0.13.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", "ecdsa", @@ -721,9 +721,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" dependencies = [ "serde", ] diff --git a/example/cosmwasm/cw721/Cargo.toml b/example/cosmwasm/cw721/Cargo.toml index 9a2cda946a..b2fb9350e7 100644 --- a/example/cosmwasm/cw721/Cargo.toml +++ b/example/cosmwasm/cw721/Cargo.toml @@ -17,9 +17,9 @@ library = [] cosmwasm-schema = "1.5.0" cosmwasm-std = { version = "1.3.1", features = ["staking", "stargate"] } cw-storage-plus = "1.2.0" -cw2981-royalties = "0.18.0" cw721 = "0.18.0" -cw721-base = "0.18.0" +cw721-base = { version = "0.18.0", features = ["library"] } +cw2981-royalties = "0.18.0" schemars = "0.8.16" serde = "1.0.195" thiserror = "1.0.56" diff --git a/example/cosmwasm/cw721/artifacts/checksums.txt b/example/cosmwasm/cw721/artifacts/checksums.txt index a88a66524a..9d03de6c94 100644 --- a/example/cosmwasm/cw721/artifacts/checksums.txt +++ b/example/cosmwasm/cw721/artifacts/checksums.txt @@ -1 +1 @@ -c55b1a4f074e7e9f969e4d14a70a74084f3e3fd938ad979bacd3c9aef94d8a0e cwerc721.wasm +d74f845ac5740f0758f98f7c02c3448ef02eba8b14fca6ff05fca5ba93e2fd31 cwerc721.wasm diff --git a/example/cosmwasm/cw721/artifacts/cwerc721.wasm b/example/cosmwasm/cw721/artifacts/cwerc721.wasm index 475accb7a5..a578175b00 100644 Binary files a/example/cosmwasm/cw721/artifacts/cwerc721.wasm and b/example/cosmwasm/cw721/artifacts/cwerc721.wasm differ diff --git a/example/cosmwasm/cw721/src/contract.rs b/example/cosmwasm/cw721/src/contract.rs index f5c1cea3d1..bed4c253b2 100644 --- a/example/cosmwasm/cw721/src/contract.rs +++ b/example/cosmwasm/cw721/src/contract.rs @@ -1,15 +1,23 @@ +use crate::error::CwErc721ContractError; +use crate::msg::CwErc721QueryMsg; +use crate::msg::{EvmMsg, EvmQueryWrapper, InstantiateMsg}; +use crate::querier::{EvmQuerier, DEFAULT_LIMIT, MAX_LIMIT}; +use crate::state::ERC721_ADDRESS; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - DepsMut, Deps, Env, MessageInfo, Response, Binary, StdResult, to_json_binary, Empty, Uint128, + to_json_binary, Binary, Deps, DepsMut, Env, Int256, MessageInfo, Response, StdError, StdResult, + Uint128, }; -use cw721::{Cw721ReceiveMsg, OwnerOfResponse, Approval, ApprovalResponse, ApprovalsResponse, OperatorResponse, ContractInfoResponse, NftInfoResponse, AllNftInfoResponse}; -use cw2981_royalties::msg::{Cw2981QueryMsg, RoyaltiesInfoResponse, CheckRoyaltiesResponse}; -use cw2981_royalties::Metadata as Cw2981Metadata; -use crate::msg::{EvmQueryWrapper, EvmMsg, InstantiateMsg, ExecuteMsg, QueryMsg}; -use crate::querier::EvmQuerier; -use crate::error::ContractError; -use crate::state::ERC721_ADDRESS; +use cw2981_royalties::msg::{CheckRoyaltiesResponse, RoyaltiesInfoResponse}; +use cw2981_royalties::{ExecuteMsg, Extension, Metadata}; +use cw721::{ + AllNftInfoResponse, Approval, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, + Cw721ReceiveMsg, NftInfoResponse, NumTokensResponse, OperatorResponse, OperatorsResponse, + OwnerOfResponse, TokensResponse, +}; +use cw721_base::QueryMsg; +use std::str::FromStr; const ERC2981_ID: &str = "0x2a55205a"; @@ -19,7 +27,7 @@ pub fn instantiate( _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> Result { ERC721_ADDRESS.save(deps.storage, &msg.erc721_address)?; Ok(Response::default()) } @@ -29,28 +37,32 @@ pub fn execute( deps: DepsMut, _env: Env, info: MessageInfo, - msg: ExecuteMsg, Empty>, -) -> Result, ContractError> { + msg: ExecuteMsg, +) -> Result, CwErc721ContractError> { match msg { - ExecuteMsg::TransferNft { recipient, token_id } => { - execute_transfer_nft(deps, info, recipient, token_id) - }, - ExecuteMsg::SendNft { contract, token_id , msg} => { - execute_send_nft(deps, info, contract, token_id, msg) - }, - ExecuteMsg::Approve { spender, token_id, expires: _ } => { - execute_approve(deps, info, spender, token_id, true) - }, - ExecuteMsg::Revoke { spender, token_id } => { - execute_approve(deps, info, spender, token_id, false) - }, - ExecuteMsg::ApproveAll { operator, expires: _ } => { - execute_approve_all(deps, info, operator, true) - }, - ExecuteMsg::RevokeAll { operator } => { - execute_approve_all(deps, info, operator, false) - }, - ExecuteMsg::Burn { token_id: _ } => { execute_burn() }, + ExecuteMsg::TransferNft { + recipient, + token_id, + } => execute_transfer_nft(deps, info, recipient, token_id), + ExecuteMsg::SendNft { + contract, + token_id, + msg, + } => execute_send_nft(deps, info, contract, token_id, msg), + ExecuteMsg::Approve { + spender, + token_id, + expires: _, + } => execute_approve(deps, info, spender, token_id), + ExecuteMsg::Revoke { token_id, .. } => { + execute_approve(deps, info, "".to_string(), token_id) + } + ExecuteMsg::ApproveAll { + operator, + expires: _, + } => execute_approve_all(deps, info, operator, true), + ExecuteMsg::RevokeAll { operator } => execute_approve_all(deps, info, operator, false), + ExecuteMsg::Burn { .. } => execute_burn(), ExecuteMsg::Mint { .. } => execute_mint(), ExecuteMsg::UpdateOwnership(_) => update_ownership(), ExecuteMsg::Extension { .. } => execute_extension(), @@ -62,9 +74,13 @@ pub fn execute_transfer_nft( info: MessageInfo, recipient: String, token_id: String, -) -> Result, ContractError> { - let mut res = transfer_nft(deps, info, recipient, token_id)?; - res = res.add_attribute("action", "transfer_nft"); +) -> Result, CwErc721ContractError> { + let mut res = transfer_nft(deps, &info, &recipient, &token_id)?; + res = res + .add_attribute("action", "transfer_nft") + .add_attribute("sender", info.sender) + .add_attribute("recipient", recipient) + .add_attribute("token_id", token_id); Ok(res) } @@ -74,16 +90,19 @@ pub fn execute_send_nft( recipient: String, token_id: String, msg: Binary, -) -> Result, ContractError> { - let mut res = transfer_nft(deps, info.clone(), recipient.clone(), token_id.clone())?; +) -> Result, CwErc721ContractError> { + let mut res = transfer_nft(deps, &info, &recipient, &token_id)?; let send = Cw721ReceiveMsg { sender: info.sender.to_string(), - token_id: token_id.clone(), + token_id: token_id.to_string(), msg, }; res = res - .add_message(send.into_cosmos_msg(recipient.clone())?) - .add_attribute("action", "send_nft"); + .add_message(send.into_cosmos_msg(recipient.to_string())?) + .add_attribute("action", "send_nft") + .add_attribute("sender", info.sender) + .add_attribute("recipient", recipient) + .add_attribute("token_id", token_id); Ok(res) } @@ -92,25 +111,28 @@ pub fn execute_approve( info: MessageInfo, spender: String, token_id: String, - approved: bool, -) -> Result, ContractError> { +) -> Result, CwErc721ContractError> { let erc_addr = ERC721_ADDRESS.load(deps.storage)?; let querier = EvmQuerier::new(&deps.querier); - let mut payload_spender = spender.clone(); - let mut action = "approve"; - if !approved { - payload_spender = "".to_string(); - action = "revoke"; - } - let payload = querier.erc721_approve_payload(payload_spender, token_id.clone())?; - let msg = EvmMsg::DelegateCallEvm { to: erc_addr, data: payload.encoded_payload }; + let payload = querier.erc721_approve_payload(spender.to_string(), token_id.to_string())?; + let msg = EvmMsg::DelegateCallEvm { + to: erc_addr, + data: payload.encoded_payload, + }; let res = Response::new() - .add_attribute("action", action) - .add_attribute("token_id", token_id) + .add_message(msg) + .add_attribute( + "action", + if !spender.is_empty() { + "approve" + } else { + "revoke" + }, + ) .add_attribute("sender", info.sender) - .add_attribute("spender", spender.clone()) - .add_message(msg); + .add_attribute("spender", spender) + .add_attribute("token_id", token_id); Ok(res) } @@ -120,148 +142,400 @@ pub fn execute_approve_all( info: MessageInfo, to: String, approved: bool, -) -> Result, ContractError> { +) -> Result, CwErc721ContractError> { let erc_addr = ERC721_ADDRESS.load(deps.storage)?; let querier = EvmQuerier::new(&deps.querier); let payload = querier.erc721_set_approval_all_payload(to.clone(), approved)?; - let msg = EvmMsg::DelegateCallEvm { to: erc_addr, data: payload.encoded_payload }; - let mut action = "approve_all"; - if !approved { - action = "revoke_all"; - } + let msg = EvmMsg::DelegateCallEvm { + to: erc_addr, + data: payload.encoded_payload, + }; let res = Response::new() - .add_attribute("action", action) - .add_attribute("operator", to) + .add_attribute( + "action", + if approved { + "approve_all" + } else { + "revoke_all" + }, + ) .add_attribute("sender", info.sender) - .add_attribute("approved", format!("{}", approved)) + .add_attribute("operator", to) .add_message(msg); Ok(res) } -pub fn execute_burn() -> Result, ContractError> { - Err(ContractError::NotSupported {}) +pub fn execute_burn() -> Result, CwErc721ContractError> { + Err(CwErc721ContractError::NotSupported {}) } -pub fn execute_mint() -> Result, ContractError> { - Err(ContractError::NotSupported {}) +pub fn execute_mint() -> Result, CwErc721ContractError> { + Err(CwErc721ContractError::NotSupported {}) } -pub fn update_ownership() -> Result, ContractError> { - Err(ContractError::NotSupported {}) +pub fn update_ownership() -> Result, CwErc721ContractError> { + Err(CwErc721ContractError::NotSupported {}) } -pub fn execute_extension() -> Result, ContractError> { - Err(ContractError::NotSupported {}) +pub fn execute_extension() -> Result, CwErc721ContractError> { + Err(CwErc721ContractError::NotSupported {}) } fn transfer_nft( deps: DepsMut, - info: MessageInfo, - recipient: String, - token_id: String, -) -> Result, ContractError> { + info: &MessageInfo, + recipient: &str, + token_id: &str, +) -> Result, CwErc721ContractError> { deps.api.addr_validate(&recipient)?; let erc_addr = ERC721_ADDRESS.load(deps.storage)?; let querier = EvmQuerier::new(&deps.querier); - let owner = querier.erc721_owner(info.sender.clone().into_string(), erc_addr.clone(), token_id.clone())?.owner; - let payload = querier.erc721_transfer_payload(owner, recipient.clone(), token_id.clone())?; - let msg = EvmMsg::DelegateCallEvm { to: erc_addr, data: payload.encoded_payload }; - let res = Response::new() - .add_attribute("sender", info.sender) - .add_attribute("recipient", recipient) - .add_attribute("token_id", token_id) - .add_message(msg); + let owner = querier + .erc721_owner( + info.sender.to_string(), + erc_addr.to_string(), + token_id.to_string(), + )? + .owner; + let payload = + querier.erc721_transfer_payload(owner, recipient.to_string(), token_id.to_string())?; + let msg = EvmMsg::DelegateCallEvm { + to: erc_addr, + data: payload.encoded_payload, + }; - Ok(res) + Ok(Response::new().add_message(msg)) } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { +pub fn query( + deps: Deps, + env: Env, + msg: QueryMsg, +) -> Result { match msg { - QueryMsg::OwnerOf { token_id, include_expired: _ } => Ok(to_json_binary(&query_owner_of(deps, env, token_id)?)?), - QueryMsg::Approval { token_id, spender, include_expired: _ } => Ok(query_approval(deps, env, token_id, spender)?), - QueryMsg::Approvals { token_id, include_expired: _ } => Ok(query_approvals(deps, env, token_id)?), - QueryMsg::Operator { owner, operator, include_expired: _ } => Ok(query_operator(deps, env, owner, operator)?), - QueryMsg::ContractInfo {} => Ok(query_contract_info(deps, env)?), - QueryMsg::NftInfo { token_id } => Ok(query_nft_info(deps, env, token_id)?), - QueryMsg::AllNftInfo { token_id, include_expired: _ } => Ok(query_all_nft_info(deps, env, token_id)?), + QueryMsg::OwnerOf { + token_id, + include_expired: _, + } => Ok(to_json_binary(&query_owner_of(deps, env, token_id)?)?), + QueryMsg::Approval { + token_id, + spender, + include_expired: _, + } => Ok(to_json_binary(&query_approval( + deps, env, token_id, spender, + )?)?), + QueryMsg::Approvals { + token_id, + include_expired: _, + } => Ok(to_json_binary(&query_approvals(deps, env, token_id)?)?), + QueryMsg::Operator { + owner, + operator, + include_expired: _, + } => Ok(to_json_binary(&query_operator( + deps, env, owner, operator, + )?)?), + QueryMsg::AllOperators { + owner, + include_expired: _, + start_after, + limit, + } => Ok(to_json_binary(&query_all_operators( + deps, + env, + owner, + start_after, + limit, + )?)?), + QueryMsg::NumTokens {} => Ok(to_json_binary(&query_num_tokens(deps, env)?)?), + QueryMsg::ContractInfo {} => Ok(to_json_binary(&query_contract_info(deps, env)?)?), + QueryMsg::NftInfo { token_id } => { + Ok(to_json_binary(&query_nft_info(deps, env, token_id)?)?) + } + QueryMsg::AllNftInfo { + token_id, + include_expired: _, + } => Ok(to_json_binary(&query_all_nft_info(deps, env, token_id)?)?), + QueryMsg::Tokens { + owner, + start_after, + limit, + } => Ok(to_json_binary(&query_tokens( + deps, + env, + owner, + start_after, + limit, + )?)?), + QueryMsg::AllTokens { start_after, limit } => Ok(to_json_binary(&query_all_tokens( + deps, + env, + start_after, + limit, + )?)?), + QueryMsg::Minter {} => Ok(to_json_binary(&query_minter()?)?), + QueryMsg::Ownership {} => Ok(to_json_binary(&query_ownership()?)?), QueryMsg::Extension { msg } => match msg { - Cw2981QueryMsg::RoyaltyInfo { + CwErc721QueryMsg::EvmAddress {} => { + Ok(to_json_binary(&ERC721_ADDRESS.load(deps.storage)?)?) + } + CwErc721QueryMsg::RoyaltyInfo { token_id, sale_price, - } => Ok(to_json_binary(&query_royalty_info(deps, env, token_id, sale_price)?)?), - Cw2981QueryMsg::CheckRoyalties {} => Ok(to_json_binary(&query_check_royalties(deps, env)?)?), + } => Ok(to_json_binary(&query_royalty_info( + deps, env, token_id, sale_price, + )?)?), + CwErc721QueryMsg::CheckRoyalties {} => { + Ok(to_json_binary(&query_check_royalties(deps, env)?)?) + } }, - _ => Err(ContractError::NotSupported { }), } } -pub fn query_owner_of(deps: Deps, env: Env, token_id: String) -> StdResult { +pub fn query_owner_of( + deps: Deps, + env: Env, + token_id: String, +) -> StdResult { let erc_addr = ERC721_ADDRESS.load(deps.storage)?; let querier = EvmQuerier::new(&deps.querier); - let owner = querier.erc721_owner(env.clone().contract.address.into_string(), erc_addr.clone(), token_id.clone())?.owner; - let approved = querier.erc721_approved(env.clone().contract.address.into_string(), erc_addr.clone(), token_id.clone())?.approved; + let owner = querier + .erc721_owner( + env.clone().contract.address.into_string(), + erc_addr.clone(), + token_id.clone(), + )? + .owner; + let approved = querier + .erc721_approved( + env.clone().contract.address.into_string(), + erc_addr.clone(), + token_id.clone(), + )? + .approved; let mut approvals: Vec = vec![]; if !approved.is_empty() { - approvals.push(Approval{spender:approved, expires: cw721::Expiration::Never {}}); + approvals.push(Approval { + spender: approved, + expires: cw721::Expiration::Never {}, + }); } - Ok(OwnerOfResponse{owner, approvals}) + Ok(OwnerOfResponse { owner, approvals }) } -pub fn query_approval(deps: Deps, env: Env, token_id: String, spender: String) -> StdResult { +pub fn query_approval( + deps: Deps, + env: Env, + token_id: String, + spender: String, +) -> StdResult { let erc_addr = ERC721_ADDRESS.load(deps.storage)?; let querier = EvmQuerier::new(&deps.querier); - let approved = querier.erc721_approved(env.clone().contract.address.into_string(), erc_addr.clone(), token_id.clone())?.approved; + let approved = querier + .erc721_approved( + env.clone().contract.address.into_string(), + erc_addr.clone(), + token_id.clone(), + )? + .approved; if !approved.is_empty() && approved == spender { - return to_json_binary(&ApprovalResponse{approval: Approval{spender, expires: cw721::Expiration::Never {}}}); + return Ok(ApprovalResponse { + approval: Approval { + spender, + expires: cw721::Expiration::Never {}, + }, + }); } - Err(cosmwasm_std::StdError::NotFound { kind: "not approved".to_string() }) + Err(StdError::generic_err("not approved")) } -pub fn query_approvals(deps: Deps, env: Env, token_id: String) -> StdResult { +pub fn query_approvals( + deps: Deps, + env: Env, + token_id: String, +) -> StdResult { let erc_addr = ERC721_ADDRESS.load(deps.storage)?; let querier = EvmQuerier::new(&deps.querier); - let approved = querier.erc721_approved(env.clone().contract.address.into_string(), erc_addr.clone(), token_id.clone())?.approved; + let approved = querier + .erc721_approved( + env.clone().contract.address.into_string(), + erc_addr.clone(), + token_id.clone(), + )? + .approved; if !approved.is_empty() { - return to_json_binary(&ApprovalsResponse{approvals: vec![Approval{spender: approved, expires: cw721::Expiration::Never {}}]}); + return Ok(ApprovalsResponse { + approvals: vec![Approval { + spender: approved, + expires: cw721::Expiration::Never {}, + }], + }); } - to_json_binary(&ApprovalsResponse{approvals: vec![]}) + Ok(ApprovalsResponse { approvals: vec![] }) } -pub fn query_operator(deps: Deps, env: Env, owner: String, operator: String) -> StdResult { +pub fn query_operator( + deps: Deps, + env: Env, + owner: String, + operator: String, +) -> StdResult { let erc_addr = ERC721_ADDRESS.load(deps.storage)?; let querier = EvmQuerier::new(&deps.querier); - let is_approved = querier.erc721_is_approved_for_all(env.clone().contract.address.into_string(), erc_addr.clone(), owner.clone(), operator.clone())?.is_approved; + let is_approved = querier + .erc721_is_approved_for_all( + env.clone().contract.address.into_string(), + erc_addr.clone(), + owner.clone(), + operator.clone(), + )? + .is_approved; if is_approved { - return to_json_binary(&OperatorResponse{approval: Approval{spender: operator.clone(), expires: cw721::Expiration::Never {}}}); + return Ok(OperatorResponse { + approval: Approval { + spender: operator.clone(), + expires: cw721::Expiration::Never {}, + }, + }); } - Err(cosmwasm_std::StdError::NotFound { kind: "not approved".to_string() }) + Err(StdError::generic_err("operator not approved".to_string())) +} + +pub fn query_all_operators( + _deps: Deps, + _env: Env, + _owner: String, + _start_after: Option, + _limit: Option, +) -> Result { + Err(CwErc721ContractError::NotSupported {}) } -pub fn query_contract_info(deps: Deps, env: Env) -> StdResult { +pub fn query_num_tokens(deps: Deps, env: Env) -> StdResult { let erc_addr = ERC721_ADDRESS.load(deps.storage)?; let querier = EvmQuerier::new(&deps.querier); - let res = querier.erc721_name_symbol(env.clone().contract.address.into_string(), erc_addr.clone())?; - to_json_binary(&ContractInfoResponse{name: res.name, symbol: res.symbol}) + let res = querier + .erc721_total_supply(env.clone().contract.address.into_string(), erc_addr.clone())?; + Ok(NumTokensResponse { + count: res.supply.u128() as u64, + }) } -pub fn query_nft_info(deps: Deps, env: Env, token_id: String) -> StdResult { +pub fn query_contract_info( + deps: Deps, + env: Env, +) -> StdResult { let erc_addr = ERC721_ADDRESS.load(deps.storage)?; let querier = EvmQuerier::new(&deps.querier); - let res = querier.erc721_uri(env.clone().contract.address.into_string(), erc_addr.clone(), token_id.clone())?; - to_json_binary(&NftInfoResponse{token_uri: Some(res.uri), extension: ""}) + let res = + querier.erc721_name_symbol(env.clone().contract.address.into_string(), erc_addr.clone())?; + Ok(ContractInfoResponse { + name: res.name, + symbol: res.symbol, + }) } -pub fn query_all_nft_info(deps: Deps, env: Env, token_id: String) -> StdResult { +pub fn query_nft_info( + deps: Deps, + env: Env, + token_id: String, +) -> StdResult> { let erc_addr = ERC721_ADDRESS.load(deps.storage)?; let querier = EvmQuerier::new(&deps.querier); - let res = querier.erc721_uri(env.clone().contract.address.into_string(), erc_addr.clone(), token_id.clone())?; - let owner_of_res = query_owner_of(deps, env, token_id)?; - to_json_binary(&AllNftInfoResponse{access: owner_of_res, info: NftInfoResponse{token_uri: Some(res.uri), extension: ""}}) + let res = querier.erc721_uri( + env.clone().contract.address.into_string(), + erc_addr.clone(), + token_id.clone(), + )?; + let royalty_info = query_royalty_info(deps, env, token_id, 100u128.into()); + Ok(NftInfoResponse { + token_uri: Some(res.uri), + extension: Some(Metadata { + image: None, + image_data: None, + external_url: None, + description: None, + name: None, + attributes: None, + background_color: None, + animation_url: None, + youtube_url: None, + royalty_percentage: if let Ok(royalty_info) = &royalty_info { + Some(royalty_info.royalty_amount.u128() as u64) + } else { + None + }, + royalty_payment_address: if let Ok(royalty_info) = royalty_info { + Some(royalty_info.address) + } else { + None + }, + }), + }) +} + +pub fn query_all_nft_info( + deps: Deps, + env: Env, + token_id: String, +) -> StdResult> { + let owner_of_res = query_owner_of(deps, env.clone(), token_id.to_string())?; + let nft_info_res = query_nft_info(deps, env, token_id)?; + Ok(AllNftInfoResponse { + access: owner_of_res, + info: nft_info_res, + }) +} + +pub fn query_tokens( + deps: Deps, + env: Env, + owner: String, + start_after: Option, + limit: Option, +) -> StdResult { + let erc_addr = ERC721_ADDRESS.load(deps.storage)?; + let querier = EvmQuerier::new(&deps.querier); + let num_tokens = query_num_tokens(deps, env.clone())?.count; + let start_after_id = Int256::from_str(&start_after.unwrap_or("-1".to_string()))?; + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + let mut cur = Int256::zero(); + let mut counter = 0; + let mut tokens: Vec = vec![]; + while counter < num_tokens && tokens.len() < limit { + let cur_str = cur.to_string(); + let t_owner = match querier.erc721_owner( + env.clone().contract.address.into_string(), + erc_addr.clone(), + cur_str.to_string(), + ) { + Ok(res) => res.owner, + Err(_) => "".to_string(), + }; + if t_owner != "" { + counter += 1; + if (owner.is_empty() || t_owner == owner) && cur > start_after_id { + tokens.push(cur_str); + } + } + cur += Int256::one(); + } + Ok(TokensResponse { tokens }) +} + +pub fn query_all_tokens( + deps: Deps, + env: Env, + start_after: Option, + limit: Option, +) -> StdResult { + query_tokens(deps, env, "".to_string(), start_after, limit) } pub fn query_royalty_info( @@ -284,11 +558,26 @@ pub fn query_royalty_info( }) } -pub fn query_check_royalties(deps: Deps, env: Env,) -> StdResult { +pub fn query_check_royalties( + deps: Deps, + env: Env, +) -> StdResult { let erc_addr = ERC721_ADDRESS.load(deps.storage)?; let querier = EvmQuerier::new(&deps.querier); - let res = querier.supports_interface(env.clone().contract.address.into_string(),erc_addr.clone(), ERC2981_ID.to_string())?; + let res = querier.supports_interface( + env.clone().contract.address.into_string(), + erc_addr.clone(), + ERC2981_ID.to_string(), + )?; Ok(CheckRoyaltiesResponse { royalty_payments: res.supported, }) -} \ No newline at end of file +} + +pub fn query_minter() -> Result, CwErc721ContractError> { + Err(CwErc721ContractError::NotSupported {}) +} + +pub fn query_ownership() -> Result, CwErc721ContractError> { + Err(CwErc721ContractError::NotSupported {}) +} diff --git a/example/cosmwasm/cw721/src/error.rs b/example/cosmwasm/cw721/src/error.rs index 4758f75d26..e270cc0e54 100644 --- a/example/cosmwasm/cw721/src/error.rs +++ b/example/cosmwasm/cw721/src/error.rs @@ -2,10 +2,10 @@ use cosmwasm_std::StdError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] -pub enum ContractError { +pub enum CwErc721ContractError { #[error("{0}")] Std(#[from] StdError), #[error("ERC721 does not have the requested functionality in specification")] NotSupported {}, -} \ No newline at end of file +} diff --git a/example/cosmwasm/cw721/src/msg.rs b/example/cosmwasm/cw721/src/msg.rs index dbddfaf3f7..5089169e17 100644 --- a/example/cosmwasm/cw721/src/msg.rs +++ b/example/cosmwasm/cw721/src/msg.rs @@ -1,10 +1,8 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{CosmosMsg, CustomMsg, CustomQuery, Uint128}; use schemars::JsonSchema; -use cosmwasm_schema::cw_serde; use serde::{Deserialize, Serialize}; -pub use cw721_base::{ExecuteMsg, QueryMsg}; - #[cw_serde] pub struct InstantiateMsg { pub erc721_address: String, @@ -61,6 +59,10 @@ pub enum EvmQuery { to: String, approved: bool, }, + Erc721TotalSupply { + caller: String, + contract_address: String, + }, Erc721NameSymbol { caller: String, contract_address: String, @@ -103,6 +105,11 @@ pub struct Erc721IsApprovedForAllResponse { pub is_approved: bool, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Erc721TotalSupplyResponse { + pub supply: Uint128, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Erc721NameSymbolResponse { pub name: String, @@ -142,4 +149,41 @@ pub enum EvmMsg { to: String, data: String, // base64 encoded }, -} \ No newline at end of file +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum CwErc721QueryMsg { + #[returns(String)] + EvmAddress {}, + + // cw2981 + /// Should be called on sale to see if royalties are owed + /// by the marketplace selling the NFT, if CheckRoyalties + /// returns true + /// See https://eips.ethereum.org/EIPS/eip-2981 + #[returns(cw2981_royalties::msg::RoyaltiesInfoResponse)] + RoyaltyInfo { + token_id: String, + // the denom of this sale must also be the denom returned by RoyaltiesInfoResponse + // this was originally implemented as a Coin + // however that would mean you couldn't buy using CW20s + // as CW20 is just mapping of addr -> balance + sale_price: Uint128, + }, + /// Called against contract to determine if this NFT + /// implements royalties. Should return a boolean as part of + /// CheckRoyaltiesResponse - default can simply be true + /// if royalties are implemented at token level + /// (i.e. always check on sale) + #[returns(cw2981_royalties::msg::CheckRoyaltiesResponse)] + CheckRoyalties {}, +} + +impl Default for CwErc721QueryMsg { + fn default() -> Self { + CwErc721QueryMsg::EvmAddress {} + } +} + +impl CustomMsg for CwErc721QueryMsg {} diff --git a/example/cosmwasm/cw721/src/querier.rs b/example/cosmwasm/cw721/src/querier.rs index 22f37bebc3..efd01de129 100644 --- a/example/cosmwasm/cw721/src/querier.rs +++ b/example/cosmwasm/cw721/src/querier.rs @@ -1,6 +1,13 @@ use cosmwasm_std::{QuerierWrapper, StdResult, Uint128}; -use crate::msg::{Route, EvmQuery, EvmQueryWrapper, ErcPayloadResponse, Erc721OwnerResponse, Erc721ApprovedResponse, Erc721IsApprovedForAllResponse, Erc721NameSymbolResponse, Erc721UriResponse, Erc721RoyaltyInfoResponse, SupportsInterfaceResponse}; +use crate::msg::{ + Erc721ApprovedResponse, Erc721IsApprovedForAllResponse, Erc721NameSymbolResponse, + Erc721OwnerResponse, Erc721RoyaltyInfoResponse, Erc721TotalSupplyResponse, Erc721UriResponse, + ErcPayloadResponse, EvmQuery, EvmQueryWrapper, Route, SupportsInterfaceResponse, +}; + +pub const DEFAULT_LIMIT: u32 = 10; +pub const MAX_LIMIT: u32 = 30; pub struct EvmQuerier<'a> { querier: &'a QuerierWrapper<'a, EvmQueryWrapper>, @@ -11,50 +18,112 @@ impl<'a> EvmQuerier<'a> { EvmQuerier { querier } } - pub fn erc721_owner(&self, caller: String, contract_address: String, token_id: String) -> StdResult { + pub fn erc721_owner( + &self, + caller: String, + contract_address: String, + token_id: String, + ) -> StdResult { let request = EvmQueryWrapper { route: Route::Evm, - query_data: EvmQuery::Erc721Owner { caller, contract_address, token_id }, + query_data: EvmQuery::Erc721Owner { + caller, + contract_address, + token_id, + }, } .into(); self.querier.query(&request) } - pub fn erc721_approved(&self, caller: String, contract_address: String, token_id: String) -> StdResult { + pub fn erc721_approved( + &self, + caller: String, + contract_address: String, + token_id: String, + ) -> StdResult { let request = EvmQueryWrapper { route: Route::Evm, - query_data: EvmQuery::Erc721Approved { caller, contract_address, token_id }, + query_data: EvmQuery::Erc721Approved { + caller, + contract_address, + token_id, + }, + } + .into(); + + self.querier.query(&request) + } + + pub fn erc721_is_approved_for_all( + &self, + caller: String, + contract_address: String, + owner: String, + operator: String, + ) -> StdResult { + let request = EvmQueryWrapper { + route: Route::Evm, + query_data: EvmQuery::Erc721IsApprovedForAll { + caller, + contract_address, + owner, + operator, + }, } .into(); self.querier.query(&request) } - pub fn erc721_is_approved_for_all(&self, caller: String, contract_address: String, owner: String, operator: String) -> StdResult { + pub fn erc721_total_supply( + &self, + caller: String, + contract_address: String, + ) -> StdResult { let request = EvmQueryWrapper { route: Route::Evm, - query_data: EvmQuery::Erc721IsApprovedForAll { caller, contract_address, owner, operator }, + query_data: EvmQuery::Erc721TotalSupply { + caller, + contract_address, + }, } .into(); self.querier.query(&request) } - pub fn erc721_name_symbol(&self, caller: String, contract_address: String) -> StdResult { + pub fn erc721_name_symbol( + &self, + caller: String, + contract_address: String, + ) -> StdResult { let request = EvmQueryWrapper { route: Route::Evm, - query_data: EvmQuery::Erc721NameSymbol { caller, contract_address }, + query_data: EvmQuery::Erc721NameSymbol { + caller, + contract_address, + }, } .into(); self.querier.query(&request) } - pub fn erc721_uri(&self, caller: String, contract_address: String, token_id: String,) -> StdResult { + pub fn erc721_uri( + &self, + caller: String, + contract_address: String, + token_id: String, + ) -> StdResult { let request = EvmQueryWrapper { route: Route::Evm, - query_data: EvmQuery::Erc721Uri { caller, contract_address, token_id }, + query_data: EvmQuery::Erc721Uri { + caller, + contract_address, + token_id, + }, } .into(); @@ -62,11 +131,18 @@ impl<'a> EvmQuerier<'a> { } // returns base64-encoded bytes - pub fn erc721_transfer_payload(&self, from: String, recipient: String, token_id: String) -> StdResult { + pub fn erc721_transfer_payload( + &self, + from: String, + recipient: String, + token_id: String, + ) -> StdResult { let request = EvmQueryWrapper { route: Route::Evm, query_data: EvmQuery::Erc721TransferPayload { - from, recipient, token_id, + from, + recipient, + token_id, }, } .into(); @@ -75,12 +151,14 @@ impl<'a> EvmQuerier<'a> { } // returns base64-encoded bytes - pub fn erc721_approve_payload(&self, spender: String, token_id: String) -> StdResult { + pub fn erc721_approve_payload( + &self, + spender: String, + token_id: String, + ) -> StdResult { let request = EvmQueryWrapper { route: Route::Evm, - query_data: EvmQuery::Erc721ApprovePayload { - spender, token_id, - }, + query_data: EvmQuery::Erc721ApprovePayload { spender, token_id }, } .into(); @@ -88,10 +166,14 @@ impl<'a> EvmQuerier<'a> { } // returns base64-encoded bytes - pub fn erc721_set_approval_all_payload(&self, to: String, approved: bool) -> StdResult { + pub fn erc721_set_approval_all_payload( + &self, + to: String, + approved: bool, + ) -> StdResult { let request = EvmQueryWrapper { route: Route::Evm, - query_data: EvmQuery::Erc721SetApprovalAllPayload { to, approved, }, + query_data: EvmQuery::Erc721SetApprovalAllPayload { to, approved }, } .into(); @@ -127,10 +209,14 @@ impl<'a> EvmQuerier<'a> { ) -> StdResult { let request = EvmQueryWrapper { route: Route::Evm, - query_data: EvmQuery::SupportsInterface { caller, interface_id, contract_address, }, + query_data: EvmQuery::SupportsInterface { + caller, + interface_id, + contract_address, + }, } .into(); self.querier.query(&request) } -} \ No newline at end of file +} diff --git a/wasmbinding/queries.go b/wasmbinding/queries.go index 485b93d0de..6ed1b157cc 100644 --- a/wasmbinding/queries.go +++ b/wasmbinding/queries.go @@ -239,21 +239,24 @@ func (qp QueryPlugin) HandleEVMQuery(ctx sdk.Context, queryData json.RawMessage) case parsedQuery.ERC721IsApprovedForAll != nil: c := parsedQuery.ERC721IsApprovedForAll return qp.evmHandler.HandleERC721IsApprovedForAll(ctx, c.Caller, c.ContractAddress, c.Owner, c.Operator) + case parsedQuery.ERC721TotalSupply != nil: + c := parsedQuery.ERC721TotalSupply + return qp.evmHandler.HandleERC721TotalSupply(ctx, c.Caller, c.ContractAddress) case parsedQuery.ERC721NameSymbol != nil: c := parsedQuery.ERC721NameSymbol return qp.evmHandler.HandleERC721NameSymbol(ctx, c.Caller, c.ContractAddress) case parsedQuery.ERC721Uri != nil: c := parsedQuery.ERC721Uri return qp.evmHandler.HandleERC721Uri(ctx, c.Caller, c.ContractAddress, c.TokenID) + case parsedQuery.ERC721RoyaltyInfo != nil: + c := parsedQuery.ERC721RoyaltyInfo + return qp.evmHandler.HandleERC721RoyaltyInfo(ctx, c.Caller, c.ContractAddress, c.TokenID, c.SalePrice) case parsedQuery.GetEvmAddress != nil: c := parsedQuery.GetEvmAddress return qp.evmHandler.HandleGetEvmAddress(ctx, c.SeiAddress) case parsedQuery.GetSeiAddress != nil: c := parsedQuery.GetSeiAddress return qp.evmHandler.HandleGetSeiAddress(ctx, c.EvmAddress) - case parsedQuery.ERC721RoyaltyInfo != nil: - c := parsedQuery.ERC721RoyaltyInfo - return qp.evmHandler.HandleERC721RoyaltyInfo(ctx, c.Caller, c.ContractAddress, c.TokenID, c.SalePrice) case parsedQuery.SupportsInterface != nil: c := parsedQuery.SupportsInterface return qp.evmHandler.HandleSupportsInterface(ctx, c.Caller, c.InterfaceID, c.ContractAddress) diff --git a/x/evm/artifacts/erc721/cwerc721.wasm b/x/evm/artifacts/erc721/cwerc721.wasm index 475accb7a5..a578175b00 100644 Binary files a/x/evm/artifacts/erc721/cwerc721.wasm and b/x/evm/artifacts/erc721/cwerc721.wasm differ diff --git a/x/evm/client/wasm/bindings/queries.go b/x/evm/client/wasm/bindings/queries.go index 2e1ad292ad..420f1a81a3 100644 --- a/x/evm/client/wasm/bindings/queries.go +++ b/x/evm/client/wasm/bindings/queries.go @@ -18,11 +18,12 @@ type SeiEVMQuery struct { ERC721SetApprovalAllPayload *ERC721SetApprovalAllPayloadRequest `json:"erc721_set_approval_all_payload,omitempty"` ERC721Approved *ERC721ApprovedRequest `json:"erc721_approved,omitempty"` ERC721IsApprovedForAll *ERC721IsApprovedForAllRequest `json:"erc721_is_approved_for_all,omitempty"` + ERC721TotalSupply *ERC721TotalSupplyRequest `json:"erc721_total_supply,omitempty"` ERC721NameSymbol *ERC721NameSymbolRequest `json:"erc721_name_symbol,omitempty"` ERC721Uri *ERC721UriRequest `json:"erc721_uri,omitempty"` + ERC721RoyaltyInfo *ERC721RoyaltyInfoRequest `json:"erc721_royalty_info,omitempty"` GetEvmAddress *GetEvmAddressRequest `json:"get_evm_address,omitempty"` GetSeiAddress *GetSeiAddressRequest `json:"get_sei_address,omitempty"` - ERC721RoyaltyInfo *ERC721RoyaltyInfoRequest `json:"erc721_royalty_info,omitempty"` SupportsInterface *SupportsInterfaceRequest `json:"supports_interface,omitempty"` } @@ -99,6 +100,11 @@ type ERC721IsApprovedForAllRequest struct { Operator string `json:"operator"` } +type ERC721TotalSupplyRequest struct { + Caller string `json:"caller"` + ContractAddress string `json:"contract_address"` +} + type ERC721NameSymbolRequest struct { Caller string `json:"caller"` ContractAddress string `json:"contract_address"` @@ -110,6 +116,13 @@ type ERC721UriRequest struct { TokenID string `json:"token_id"` } +type ERC721RoyaltyInfoRequest struct { + Caller string `json:"caller"` + ContractAddress string `json:"contract_address"` + TokenID string `json:"token_id"` + SalePrice *sdk.Int `json:"sale_price"` +} + type GetEvmAddressRequest struct { SeiAddress string `json:"sei_address"` } @@ -118,13 +131,6 @@ type GetSeiAddressRequest struct { EvmAddress string `json:"evm_address"` } -type ERC721RoyaltyInfoRequest struct { - Caller string `json:"caller"` - ContractAddress string `json:"contract_address"` - TokenID string `json:"token_id"` - SalePrice *sdk.Int `json:"sale_price"` -} - type SupportsInterfaceRequest struct { Caller string `json:"caller"` ContractAddress string `json:"contract_address"` @@ -166,6 +172,10 @@ type ERC721IsApprovedForAllResponse struct { IsApproved bool `json:"is_approved"` } +type ERC721TotalSupplyResponse struct { + Supply *sdk.Int `json:"supply"` +} + type ERC721NameSymbolResponse struct { Name string `json:"name"` Symbol string `json:"symbol"` @@ -175,6 +185,11 @@ type ERC721UriResponse struct { Uri string `json:"uri"` } +type ERC721RoyaltyInfoResponse struct { + Receiver string `json:"receiver"` + RoyaltyAmount *sdk.Int `json:"royalty_amount"` +} + type GetEvmAddressResponse struct { EvmAddress string `json:"evm_address"` Associated bool `json:"associated"` @@ -185,11 +200,6 @@ type GetSeiAddressResponse struct { Associated bool `json:"associated"` } -type ERC721RoyaltyInfoResponse struct { - Receiver string `json:"receiver"` - RoyaltyAmount *sdk.Int `json:"royalty_amount"` -} - type SupportsInterfaceResponse struct { Supported bool `json:"supported"` } diff --git a/x/evm/client/wasm/query.go b/x/evm/client/wasm/query.go index 1fba5c5b39..f93597d90a 100644 --- a/x/evm/client/wasm/query.go +++ b/x/evm/client/wasm/query.go @@ -175,7 +175,7 @@ func (h *EVMQueryHandler) HandleERC721Owner(ctx sdk.Context, caller string, cont } t, ok := sdk.NewIntFromString(tokenId) if !ok { - return nil, errors.New("invalid token ID for ERC20, must be a big Int") + return nil, errors.New("invalid token ID for ERC721, must be a big Int") } bz, err := abi.Pack("ownerOf", t.BigInt()) if err != nil { @@ -213,7 +213,7 @@ func (h *EVMQueryHandler) HandleERC721TransferPayload(ctx sdk.Context, from stri } t, ok := sdk.NewIntFromString(tokenId) if !ok { - return nil, errors.New("invalid token ID for ERC20, must be a big Int") + return nil, errors.New("invalid token ID for ERC721, must be a big Int") } bz, err := abi.Pack("transferFrom", fromEvmAddr, toEvmAddr, t.BigInt()) if err != nil { @@ -239,7 +239,7 @@ func (h *EVMQueryHandler) HandleERC721ApprovePayload(ctx sdk.Context, spender st } t, ok := sdk.NewIntFromString(tokenId) if !ok { - return nil, errors.New("invalid token ID for ERC20, must be a big Int") + return nil, errors.New("invalid token ID for ERC721, must be a big Int") } bz, err := abi.Pack("approve", spenderEvmAddr, t.BigInt()) if err != nil { @@ -362,7 +362,7 @@ func (h *EVMQueryHandler) HandleERC721Approved(ctx sdk.Context, caller string, c } t, ok := sdk.NewIntFromString(tokenId) if !ok { - return nil, errors.New("invalid token ID for ERC20, must be a big Int") + return nil, errors.New("invalid token ID for ERC721, must be a big Int") } bz, err := abi.Pack("getApproved", t.BigInt()) if err != nil { @@ -419,6 +419,33 @@ func (h *EVMQueryHandler) HandleERC721IsApprovedForAll(ctx sdk.Context, caller s return json.Marshal(response) } +func (h *EVMQueryHandler) HandleERC721TotalSupply(ctx sdk.Context, caller string, contractAddress string) ([]byte, error) { + callerAddr, err := sdk.AccAddressFromBech32(caller) + if err != nil { + return nil, err + } + contract := common.HexToAddress(contractAddress) + abi, err := cw721.Cw721MetaData.GetAbi() + if err != nil { + return nil, err + } + bz, err := abi.Pack("totalSupply") + if err != nil { + return nil, err + } + res, err := h.k.StaticCallEVM(ctx, callerAddr, &contract, bz) + if err != nil { + return nil, err + } + typed, err := abi.Unpack("totalSupply", res) + if err != nil { + return nil, err + } + totalSupply := sdk.NewIntFromBigInt(typed[0].(*big.Int)) + response := bindings.ERC721TotalSupplyResponse{Supply: &totalSupply} + return json.Marshal(response) +} + func (h *EVMQueryHandler) HandleERC721NameSymbol(ctx sdk.Context, caller string, contractAddress string) ([]byte, error) { callerAddr, err := sdk.AccAddressFromBech32(caller) if err != nil { @@ -466,7 +493,7 @@ func (h *EVMQueryHandler) HandleERC721Uri(ctx sdk.Context, caller string, contra } t, ok := sdk.NewIntFromString(tokenId) if !ok { - return nil, errors.New("invalid token ID for ERC20, must be a big Int") + return nil, errors.New("invalid token ID for ERC721, must be a big Int") } contract := common.HexToAddress(contractAddress) abi, err := cw721.Cw721MetaData.GetAbi() diff --git a/x/evm/client/wasm/query_test.go b/x/evm/client/wasm/query_test.go index 6ef307da93..194aec2cc4 100644 --- a/x/evm/client/wasm/query_test.go +++ b/x/evm/client/wasm/query_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "math/big" "os" + "regexp" "strings" "testing" @@ -194,6 +195,22 @@ func TestHandleERC721IsApprovedForAll(t *testing.T) { require.NotEmpty(t, res2) } +func TestHandleERC721TotalSupply(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + privKey := testkeeper.MockPrivateKey() + res, _ := deployContract(t, ctx, k, "../../../../example/contracts/erc721/DummyERC721.bin", privKey) + addr1, e1 := testkeeper.MockAddressPair() + k.SetAddressMapping(ctx, addr1, e1) + receipt, err := k.GetReceipt(ctx, common.HexToHash(res.Hash)) + require.Nil(t, err) + contractAddr := common.HexToAddress(receipt.ContractAddress) + h := wasm.NewEVMQueryHandler(k) + res2, err := h.HandleERC721TotalSupply(ctx, addr1.String(), contractAddr.String()) + require.Nil(t, err) + require.NotEmpty(t, res2) + require.Equal(t, string(res2), "{\"supply\":\"101\"}") +} + func TestHandleERC721NameSymbol(t *testing.T) { k, ctx := testkeeper.MockEVMKeeper() privKey := testkeeper.MockPrivateKey() @@ -225,6 +242,24 @@ func TestHandleERC721TokenURI(t *testing.T) { require.NotEmpty(t, res2) } +func TestHandleERC721RoyaltyInfo(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + privKey := testkeeper.MockPrivateKey() + res, _ := deployContract(t, ctx, k, "../../../../example/contracts/erc721/DummyERC721.bin", privKey) + addr1, e1 := testkeeper.MockAddressPair() + k.SetAddressMapping(ctx, addr1, e1) + receipt, err := k.GetReceipt(ctx, common.HexToHash(res.Hash)) + require.Nil(t, err) + contractAddr := common.HexToAddress(receipt.ContractAddress) + h := wasm.NewEVMQueryHandler(k) + value := types.NewInt(100) + res2, err := h.HandleERC721RoyaltyInfo(ctx, addr1.String(), contractAddr.String(), "1", &value) + require.Nil(t, err) + require.NotEmpty(t, res2) + match, _ := regexp.MatchString(`{"receiver":"sei\w{39}","royalty_amount":"5"}`, string(res2)) + require.True(t, match) +} + func TestGetAddress(t *testing.T) { k, ctx := testkeeper.MockEVMKeeper() seiAddr1, evmAddr1 := testkeeper.MockAddressPair()