Skip to content

Commit

Permalink
Update ERC721 pointer contract (sei-protocol#1671)
Browse files Browse the repository at this point in the history
* Update ERC721 pointer contract

* Handle contract compile warnings

* Add abi/bin for ERC721 pointer

* Fix tests

* Fix bug: handle str from extractAsBytes that preserves `"`

* Fix test and update binary for CW721ERC721Pointer
  • Loading branch information
dvli2007 authored May 16, 2024
1 parent 045251b commit b096dc7
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 21 deletions.
65 changes: 52 additions & 13 deletions contracts/src/CW721ERC721Pointer.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
Expand All @@ -21,6 +21,9 @@ contract CW721ERC721Pointer is ERC721,ERC2981 {
IJson public JsonPrecompile;
IAddr public AddrPrecompile;

error NotImplementedOnCosmwasmContract(string method);
error NotImplemented(string method);

constructor(string memory Cw721Address_, string memory name_, string memory symbol_) ERC721(name_, symbol_) {
WasmdPrecompile = IWasmd(WASMD_PRECOMPILE_ADDRESS);
JsonPrecompile = IJson(JSON_PRECOMPILE_ADDRESS);
Expand All @@ -41,11 +44,22 @@ contract CW721ERC721Pointer is ERC721,ERC2981 {
if (owner == address(0)) {
revert ERC721InvalidOwner(address(0));
}
uint256 numTokens = 0;
string memory startAfter;
string memory ownerAddr = _formatPayload("owner", _doubleQuotes(AddrPrecompile.getSeiAddr(owner)));
string memory req = _curlyBrace(_formatPayload("tokens", _curlyBrace(ownerAddr)));
bytes memory response = WasmdPrecompile.query(Cw721Address, bytes(req));
bytes[] memory tokens = JsonPrecompile.extractAsBytesList(response, "tokens");
return tokens.length;
uint256 tokensLength = tokens.length;
while (tokensLength > 0) {
numTokens += tokensLength;
startAfter = _formatPayload("start_after", string(tokens[tokensLength-1]));
req = _curlyBrace(_formatPayload("tokens", _curlyBrace(_join(ownerAddr, startAfter, ","))));
response = WasmdPrecompile.query(Cw721Address, bytes(req));
tokens = JsonPrecompile.extractAsBytesList(response, "tokens");
tokensLength = tokens.length;
}
return numTokens;
}

function ownerOf(uint256 tokenId) public view override returns (address) {
Expand Down Expand Up @@ -82,22 +96,12 @@ contract CW721ERC721Pointer is ERC721,ERC2981 {
return false;
}

function tokenURI(uint256 tokenId) public view override returns (string memory) {
// revert if token isn't owned
ownerOf(tokenId);
string memory tId = _formatPayload("token_id", _doubleQuotes(Strings.toString(tokenId)));
string memory req = _curlyBrace(_formatPayload("nft_info", _curlyBrace(tId)));
bytes memory response = WasmdPrecompile.query(Cw721Address, bytes(req));
bytes memory uri = JsonPrecompile.extractAsBytes(response, "token_uri");
return string(uri);
}

// 2981
function royaltyInfo(uint256 tokenId, uint256 salePrice) public view override returns (address, uint256) {
bytes memory checkRoyaltyResponse = WasmdPrecompile.query(Cw721Address, bytes("{\"extension\":{\"msg\":{\"check_royalties\":{}}}}"));
bytes memory isRoyaltyImplemented = JsonPrecompile.extractAsBytes(checkRoyaltyResponse, "royalty_payments");
if (keccak256(isRoyaltyImplemented) != keccak256("true")) {
revert("royalty info not implemented on the underlying CosmWasm contract");
revert NotImplementedOnCosmwasmContract("royalty_info");
}
string memory tId = _formatPayload("token_id", _doubleQuotes(Strings.toString(tokenId)));
string memory sPrice = _formatPayload("sale_price", _doubleQuotes(Strings.toString(salePrice)));
Expand All @@ -112,6 +116,31 @@ contract CW721ERC721Pointer is ERC721,ERC2981 {
return (AddrPrecompile.getEvmAddr(string(addr)), amt);
}

function tokenURI(uint256 tokenId) public view override returns (string memory) {
// revert if token isn't owned
ownerOf(tokenId);
string memory tId = _formatPayload("token_id", _doubleQuotes(Strings.toString(tokenId)));
string memory req = _curlyBrace(_formatPayload("nft_info", _curlyBrace(tId)));
bytes memory response = WasmdPrecompile.query(Cw721Address, bytes(req));
bytes memory uri = JsonPrecompile.extractAsBytes(response, "token_uri");
return string(uri);
}

// 721-Enumerable
function totalSupply() public view virtual returns (uint256) {
string memory req = _curlyBrace(_formatPayload("num_tokens", "{}"));
bytes memory response = WasmdPrecompile.query(Cw721Address, bytes(req));
return JsonPrecompile.extractAsUint256(response, "count");
}

function tokenOfOwnerByIndex(address, uint256) public view virtual returns (uint256) {
revert NotImplemented("tokenOfOwnerByIndex");
}

function tokenByIndex(uint256) public view virtual returns (uint256) {
revert NotImplemented("tokenByIndex");
}

// Transactions
function transferFrom(address from, address to, uint256 tokenId) public override {
if (to == address(0)) {
Expand Down Expand Up @@ -156,6 +185,16 @@ contract CW721ERC721Pointer is ERC721,ERC2981 {
return ret;
}

function _queryContractInfo() internal view virtual returns (string memory, string memory) {
string memory req = _curlyBrace(_formatPayload("contract_info", "{}"));
bytes memory response = WasmdPrecompile.query(Cw721Address, bytes(req));
bytes memory respName = JsonPrecompile.extractAsBytes(response, "name");
bytes memory respSymbol = JsonPrecompile.extractAsBytes(response, "symbol");
string memory nameStr = string(respName);
string memory symbolStr = string(respSymbol);
return (nameStr, symbolStr);
}

function _formatPayload(string memory key, string memory value) internal pure returns (string memory) {
return _join(_doubleQuotes(key), value, ":");
}
Expand Down
34 changes: 29 additions & 5 deletions contracts/test/CW721ERC721PointerTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,39 @@ contract CW721ERC721PointerTest is Test {
abi.encodeWithSignature("query(string,bytes)", MockCWContractAddress, bytes("{\"tokens\":{\"owner\":\"sei19zhelek4q5lt4zam8mcarmgv92vzgqd3ux32jw\"}}")),
abi.encode("{\"tokens\":[\"a\",\"b\"]}")
);
bytes[] memory response = new bytes[](2);
response[0] = bytes("a");
response[1] = bytes("b");
vm.mockCall(
WASMD_PRECOMPILE_ADDRESS,
abi.encodeWithSignature("query(string,bytes)", MockCWContractAddress, bytes("{\"tokens\":{\"owner\":\"sei19zhelek4q5lt4zam8mcarmgv92vzgqd3ux32jw\",\"start_after\":\"b\"}}")),
abi.encode("{\"tokens\":[\"c\",\"d\"]}")
);
vm.mockCall(
WASMD_PRECOMPILE_ADDRESS,
abi.encodeWithSignature("query(string,bytes)", MockCWContractAddress, bytes("{\"tokens\":{\"owner\":\"sei19zhelek4q5lt4zam8mcarmgv92vzgqd3ux32jw\",\"start_after\":\"d\"}}")),
abi.encode("{\"tokens\":[]}")
);
bytes[] memory resp1 = new bytes[](2);
bytes[] memory resp2 = new bytes[](2);
bytes[] memory resp3 = new bytes[](0);
resp1[0] = bytes("\"a\"");
resp1[1] = bytes("\"b\"");
resp2[0] = bytes("\"c\"");
resp2[1] = bytes("\"d\"");
vm.mockCall(
JSON_PRECOMPILE_ADDRESS,
abi.encodeWithSignature("extractAsBytesList(bytes,string)", bytes("{\"tokens\":[\"a\",\"b\"]}"), "tokens"),
abi.encode(response)
abi.encode(resp1)
);
vm.mockCall(
JSON_PRECOMPILE_ADDRESS,
abi.encodeWithSignature("extractAsBytesList(bytes,string)", bytes("{\"tokens\":[\"c\",\"d\"]}"), "tokens"),
abi.encode(resp2)
);
vm.mockCall(
JSON_PRECOMPILE_ADDRESS,
abi.encodeWithSignature("extractAsBytesList(bytes,string)", bytes("{\"tokens\":[]}"), "tokens"),
abi.encode(resp3)
);
assertEq(pointer.balanceOf(MockCallerEVMAddr), 2);
assertEq(pointer.balanceOf(MockCallerEVMAddr), 4);
}

function testOwnerOf() public {
Expand Down
Loading

0 comments on commit b096dc7

Please sign in to comment.