Skip to content

Commit

Permalink
Support ERC/CW2981 in pointer contracts (sei-protocol#1669)
Browse files Browse the repository at this point in the history
* Support ERC/CW2981 in pointer contracts

* integration tests
  • Loading branch information
codchen authored May 15, 2024
1 parent 168286f commit 3be4978
Show file tree
Hide file tree
Showing 28 changed files with 1,800 additions and 30 deletions.
32 changes: 31 additions & 1 deletion contracts/src/CW721ERC721Pointer.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IWasmd} from "./precompiles/IWasmd.sol";
import {IJson} from "./precompiles/IJson.sol";
import {IAddr} from "./precompiles/IAddr.sol";

contract CW721ERC721Pointer is ERC721 {
contract CW721ERC721Pointer is ERC721,ERC2981 {

address constant WASMD_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000001002;
address constant JSON_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000001003;
Expand All @@ -26,6 +28,14 @@ contract CW721ERC721Pointer is ERC721 {
Cw721Address = Cw721Address_;
}

function supportsInterface(bytes4 interfaceId) public pure override(ERC721, ERC2981) returns (bool) {
return
interfaceId == type(IERC2981).interfaceId ||
interfaceId == type(IERC165).interfaceId ||
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId;
}

// Queries
function balanceOf(address owner) public view override returns (uint256) {
if (owner == address(0)) {
Expand Down Expand Up @@ -82,6 +92,26 @@ contract CW721ERC721Pointer is ERC721 {
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");
}
string memory tId = _formatPayload("token_id", _doubleQuotes(Strings.toString(tokenId)));
string memory sPrice = _formatPayload("sale_price", _doubleQuotes(Strings.toString(salePrice)));
string memory req = _curlyBrace(_formatPayload("royalty_info", _curlyBrace(_join(tId, sPrice, ","))));
string memory fullReq = _curlyBrace(_formatPayload("extension", _curlyBrace(_formatPayload("msg", req))));
bytes memory response = WasmdPrecompile.query(Cw721Address, bytes(fullReq));
bytes memory addr = JsonPrecompile.extractAsBytes(response, "address");
uint256 amt = JsonPrecompile.extractAsUint256(response, "royalty_amount");
if (addr.length == 0) {
return (address(0), amt);
}
return (AddrPrecompile.getEvmAddr(string(addr)), amt);
}

// Transactions
function transferFrom(address from, address to, uint256 tokenId) public override {
if (to == address(0)) {
Expand Down
Binary file added contracts/wasm/cw2981_royalties.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion example/contracts/README
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ make devtools

# Compile sol
solc --bin -o example/contracts/simplestorage example/contracts/simplestorage/SimpleStorage.sol
solc @openzeppelin=contracts/lib/openzeppelin-contracts --bin -o x/evm/artifacts contracts/src/CW721ERC721Wrapper.sol
solc @openzeppelin=contracts/lib/openzeppelin-contracts --bin -o x/evm/artifacts/cw721 contracts/src/CW721ERC721Pointer.sol

# Generate ABI in Go
solc --abi -o example/contracts/simplestorage example/contracts/simplestorage/SimpleStorage.sol
Expand Down
1 change: 1 addition & 0 deletions example/contracts/erc2981/ERC2981Example.abi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"numerator","type":"uint256"},{"internalType":"uint256","name":"denominator","type":"uint256"}],"name":"ERC2981InvalidDefaultRoyalty","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC2981InvalidDefaultRoyaltyReceiver","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"numerator","type":"uint256"},{"internalType":"uint256","name":"denominator","type":"uint256"}],"name":"ERC2981InvalidTokenRoyalty","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC2981InvalidTokenRoyaltyReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"owner","type":"address"}],"name":"ERC721IncorrectOwner","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721InsufficientApproval","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC721InvalidApprover","type":"error"},{"inputs":[{"internalType":"address","name":"operator","type":"address"}],"name":"ERC721InvalidOperator","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"ERC721InvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC721InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC721InvalidSender","type":"error"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721NonexistentToken","type":"error"},{"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":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","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":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","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":"","type":"address"},{"internalType":"uint256","name":"","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":"address","name":"receiver","type":"address"}],"name":"setDefaultRoyalty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","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"}]
1 change: 1 addition & 0 deletions example/contracts/erc2981/ERC2981Example.bin

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions example/contracts/erc2981/ERC2981Example.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

contract ERC2981Example is ERC721,ERC2981 {
constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {
}

function supportsInterface(bytes4 interfaceId) public pure override(ERC721, ERC2981) returns (bool) {
return
interfaceId == type(IERC2981).interfaceId ||
interfaceId == type(IERC165).interfaceId ||
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId;
}

function setDefaultRoyalty(address receiver) public {
_setDefaultRoyalty(receiver, 10000);
}
}
Loading

0 comments on commit 3be4978

Please sign in to comment.