Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPN-24 ipnft contract allows metadata cid updates #165

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,14 @@ You can place required env vars in your `.env` file and run `source .env` to get
- to rollout a new upgrade on a live network without calling the proxy's upgrade function, you can use `forge script script/UpgradeImplementation.s.sol:DeployImplementation` and invoke the upgrade function manually (e.g. from your multisig)
- for the "real" thing you'll need to add `-f` and `--private-key` and finally `--broadcast` params .

### Deploy for local development
### Deploying everything locally

#### Quickstart
You need Docker.

- You can use the shell script `./setupLocal.sh` to deploy all contracts and add the optional `-f` or `--fixture` flag to also run the fixture scripts.
#### Automatically

- `yarn localenv` sets up *everything*
- use `./setupLocal.sh` to deploy all contracts. Add the optional `-f` or `--fixture` flag to also run the fixture scripts to tokenize one IPNFT or `-fx` to create two crowdsale instances.

#### Manual

Expand Down
2 changes: 1 addition & 1 deletion deploy/001-ipfs-config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ ipfs config --json Gateway.HTTPHeaders.Access-Control-Allow-Credentials '["true"

# https://web3.storage/docs/reference/peering/
# allows us to also pull w3s / pinata content from our local machine
ipfs config --json Peering.Peers '[{"ID": "bafzbeibhqavlasjc7dvbiopygwncnrtvjd2xmryk5laib7zyjor6kf3avm","Addrs": ["/dns4/elastic.dag.house/tcp/443/wss"]},{"ID": "QmWaik1eJcGHq1ybTWe7sezRfqKNcDRNkeBaLnGwQJz1Cj","Addrs": ["/dnsaddr/fra1-1.hostnodes.pinata.cloud"]},{"ID": "QmNfpLrQQZr5Ns9FAJKpyzgnDL2GgC6xBug1yUZozKFgu4","Addrs": ["/dnsaddr/fra1-2.hostnodes.pinata.cloud"]},{"ID": "QmPo1ygpngghu5it8u4Mr3ym6SEU2Wp2wA66Z91Y1S1g29","Addrs": ["/dnsaddr/fra1-3.hostnodes.pinata.cloud"]},{"ID": "QmRjLSisUCHVpFa5ELVvX3qVPfdxajxWJEHs9kN3EcxAW6","Addrs": ["/dnsaddr/nyc1-1.hostnodes.pinata.cloud"]},{"ID": "QmPySsdmbczdZYBpbi2oq2WMJ8ErbfxtkG8Mo192UHkfGP","Addrs": ["/dnsaddr/nyc1-2.hostnodes.pinata.cloud"]},{"ID": "QmSarArpxemsPESa6FNkmuu9iSE1QWqPX2R3Aw6f5jq4D5","Addrs": ["/dnsaddr/nyc1-3.hostnodes.pinata.cloud"]}]'
ipfs config --json Peering.Peers '[{"ID": "bafzbeibhqavlasjc7dvbiopygwncnrtvjd2xmryk5laib7zyjor6kf3avm","Addrs": ["/dnsaddr/elastic.dag.house"]},{"ID": "QmWaik1eJcGHq1ybTWe7sezRfqKNcDRNkeBaLnGwQJz1Cj","Addrs": ["/dnsaddr/fra1-1.hostnodes.pinata.cloud"]},{"ID": "QmNfpLrQQZr5Ns9FAJKpyzgnDL2GgC6xBug1yUZozKFgu4","Addrs": ["/dnsaddr/fra1-2.hostnodes.pinata.cloud"]},{"ID": "QmPo1ygpngghu5it8u4Mr3ym6SEU2Wp2wA66Z91Y1S1g29","Addrs": ["/dnsaddr/fra1-3.hostnodes.pinata.cloud"]},{"ID": "QmRjLSisUCHVpFa5ELVvX3qVPfdxajxWJEHs9kN3EcxAW6","Addrs": ["/dnsaddr/nyc1-1.hostnodes.pinata.cloud"]},{"ID": "QmPySsdmbczdZYBpbi2oq2WMJ8ErbfxtkG8Mo192UHkfGP","Addrs": ["/dnsaddr/nyc1-2.hostnodes.pinata.cloud"]},{"ID": "QmSarArpxemsPESa6FNkmuu9iSE1QWqPX2R3Aw6f5jq4D5","Addrs": ["/dnsaddr/nyc1-3.hostnodes.pinata.cloud"]}]'

#https://github.com/ipfs/kubo/blob/master/docs/config.md#implicit-defaults-of-gatewaypublicgateways
#axios is confused with local ipfs subdomains
Expand Down
3 changes: 2 additions & 1 deletion deploy/inittest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ $DC down --remove-orphans
sleep 5
$DC up -d
$DC ps
./setupLocal.sh -f

./setupLocal.sh -fx

cd subgraph
yarn codegen
Expand Down
12 changes: 5 additions & 7 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#https://github.com/graphprotocol/graph-node/blob/master/docker/docker-compose.yml
services:
anvil:
image: ghcr.io/foundry-rs/foundry:nightly-a470d635cfcdce68609e9dc5762a3584351bacc1
image: ghcr.io/foundry-rs/foundry:nightly-883bb1c39f56a525657116874e59e80c2b881b10
platform: linux/amd64
command:
- 'anvil --host 0.0.0.0'
ports:
- '8545:8545'

graph-node:
image: graphprotocol/graph-node:845f8fa
image: graphprotocol/graph-node:990ef4d
ports:
- '8000:8000'
- '8001:8001'
Expand All @@ -31,15 +32,15 @@ services:
GRAPH_LOG: debug
GRAPH_ALLOW_NON_DETERMINISTIC_IPFS: 1
ipfs:
image: ipfs/kubo:v0.28.0
image: ipfs/kubo:v0.30.0
ports:
- '5001:5001'
- '8080:8080'
volumes:
- ./deploy/001-ipfs-config.sh:/container-init.d/001-ipfs-config.sh
# - ./data/ipfs:/data/ipfs
postgres:
image: postgres
image: postgres:12-alpine
ports:
- '5432:5432'
command: ['postgres', '-cshared_preload_libraries=pg_stat_statements']
Expand All @@ -49,6 +50,3 @@ services:
POSTGRES_DB: graph-node
PGDATA: '/data/postgres'
POSTGRES_INITDB_ARGS: '-E UTF8 --locale=C'

volumes:
- ./data/postgres:/var/lib/postgresql/data
1 change: 0 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ src = 'src'
out = 'out'
libs = ['lib']
test = 'test'
cache_path = 'cache_forge'
solc_version = "0.8.18"
gas_reports = [
"IPNFT",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"license": "MIT",
"scripts": {
"test": "hardhat test --network hardhat",
"clean": "rm -rf out cache_forge"
"clean": "rm -rf out cache_forge",
"localenv": "./deploy/inittest.sh"
},
"devDependencies": {
"@nomicfoundation/hardhat-foundry": "^1.0.0",
Expand Down
32 changes: 21 additions & 11 deletions setupLocal.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,28 @@ set -a
. ./.env.example
set +a

fixture=0
fixtures=false
extrafixtures=false

# Parse command-line options
while [ "$#" -gt 0 ]; do
case $1 in
-f|--fixture)
fixture=1
;;
while getopts "fx" opt; do
case ${opt} in
f)
fixtures=true
;;
x)
extrafixtures=true
;;
*)
echo "Unknown option: $1"
exit 1
;;
;;
esac
shift
done

FSC="forge script -f $RPC_URL --broadcast"
shift $((OPTIND -1))

FSC="forge script --chain 31337 --rpc-url $RPC_URL --use 0.8.18 --offline --broadcast --revert-strings debug"

# Deployments
$FSC script/dev/Ipnft.s.sol:DeployIpnftSuite
Expand All @@ -34,11 +39,16 @@ $FSC script/dev/Tokens.s.sol:DeployFakeTokens
$FSC script/dev/CrowdSale.s.sol:DeployCrowdSale

# optionally: fixtures
if [ "$fixture" -eq "1" ]; then
if $fixtures; then
echo "Running fixture scripts."

$FSC script/dev/Ipnft.s.sol:FixtureIpnft
$FSC script/dev/Tokenizer.s.sol:FixtureTokenizer
$FSC script/dev/Tokenizer.s.sol:FixtureTokenizer
fi

# optionally: extra fixtures
if $extrafixtures; then
echo "Running extra fixture scripts (crowdsales)."

$FSC script/dev/CrowdSale.s.sol:FixtureCrowdSale
echo "Waiting 15 seconds until claiming plain sale..."
Expand Down
16 changes: 14 additions & 2 deletions src/IPNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { IReservable } from "./IReservable.sol";
\▓▓▓▓▓▓\▓▓ \▓▓ \▓▓\▓▓ \▓▓
*/

/// @title IPNFT V2.4
/// @title IPNFT V2.5
/// @author molecule.to
/// @notice IP-NFTs capture intellectual property to be traded and synthesized
contract IPNFT is ERC721URIStorageUpgradeable, ERC721BurnableUpgradeable, IReservable, UUPSUpgradeable, OwnableUpgradeable, PausableUpgradeable {
Expand Down Expand Up @@ -161,6 +161,18 @@ contract IPNFT is ERC721URIStorageUpgradeable, ERC721BurnableUpgradeable, IReser
emit ReadAccessGranted(tokenId, reader, until);
}

function amendMetadata(uint256 tokenId, string calldata _newTokenURI, bytes calldata authorization) external {
if (ownerOf(tokenId) != _msgSender()) {
revert Unauthorized();
}

if (!mintAuthorizer.authorizeMint(_msgSender(), _msgSender(), abi.encode(SignedMintAuthorization(tokenId, _newTokenURI, authorization)))) {
revert Unauthorized();
}

_setTokenURI(tokenId, _newTokenURI);
elmariachi111 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @notice check whether `reader` currently is able to access gated content behind `tokenId`
* @param reader the address in question
Expand All @@ -176,7 +188,7 @@ contract IPNFT is ERC721URIStorageUpgradeable, ERC721BurnableUpgradeable, IReser
(bool success,) = _msgSender().call{ value: address(this).balance }("");
require(success, "transfer failed");
}

/// @inheritdoc UUPSUpgradeable
function _authorizeUpgrade(address /*newImplementation*/ )
internal
Expand Down
23 changes: 23 additions & 0 deletions subgraph/abis/IPNFT.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@
"inputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "amendMetadata",
"inputs": [
{
"name": "tokenId",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "_newTokenURI",
"type": "string",
"internalType": "string"
},
{
"name": "authorization",
"type": "bytes",
"internalType": "bytes"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "approve",
Expand Down
21 changes: 20 additions & 1 deletion subgraph/src/ipnftMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import {
ByteArray,
crypto,
ethereum,
log,
store
} from '@graphprotocol/graph-ts'
import {
IPNFTMinted as IPNFTMintedEvent,
Reserved as ReservedEvent,
ReadAccessGranted as ReadAccessGrantedEvent,
Transfer as TransferEvent
Transfer as TransferEvent,
MetadataUpdate as MetadataUpdateEvent,
IPNFT as IPNFTContract
} from '../generated/IPNFT/IPNFT'
import { Ipnft, Reservation, CanRead } from '../generated/schema'

Expand Down Expand Up @@ -80,3 +83,19 @@ export function handleMint(event: IPNFTMintedEvent): void {
store.remove('Reservation', event.params.tokenId.toString())
ipnft.save()
}

export function handleMetadataUpdated(event: MetadataUpdateEvent): void {
let ipnft = Ipnft.load(event.params._tokenId.toString())
if (!ipnft) {
log.error('ipnft {} not found', [event.params._tokenId.toString()])
return
}

//erc4906 is not emitting the new url, we must query it ourselves
let _ipnftContract = IPNFTContract.bind(event.params._event.address);
let newUri = _ipnftContract.tokenURI(event.params._tokenId)

ipnft.tokenURI = newUri
ipnft.save()
}

2 changes: 2 additions & 0 deletions subgraph/subgraph.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ dataSources:
handler: handleTransfer
- event: ReadAccessGranted(indexed uint256,indexed address,uint256)
handler: handleReadAccess
- event: MetadataUpdate(uint256)
handler: handleMetadataUpdated
file: ./src/ipnftMapping.ts
- kind: ethereum/contract
name: SchmackoSwap
Expand Down
27 changes: 27 additions & 0 deletions test/IPNFT.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ contract IPNFTTest is IPNFTMintHelper {
event IPNFTMinted(address indexed owner, uint256 indexed tokenId, string tokenURI, string symbol);
event SymbolUpdated(uint256 indexed tokenId, string symbol);
event ReadAccessGranted(uint256 indexed tokenId, address indexed reader, uint256 until);
event MetadataUpdate(uint256 tokenId);

IPNFT internal ipnft;

Expand Down Expand Up @@ -216,4 +217,30 @@ contract IPNFTTest is IPNFTMintHelper {
vm.warp(block.timestamp + 60);
assertFalse(ipnft.canRead(bob, tokenId));
}

function testOwnerCanAmendMetadataAfterSignoff() public {
mintAToken(ipnft, alice);

vm.startPrank(deployer);
ipnft.setAuthorizer(new SignedMintAuthorizer(deployer));
vm.stopPrank();

bytes32 authMessageHash = ECDSA.toEthSignedMessageHash(keccak256(abi.encodePacked(alice, alice, uint256(1), "ipfs://QmNewUri")));
(uint8 v, bytes32 r, bytes32 s) = vm.sign(deployerPk, authMessageHash);
bytes memory authorization = abi.encodePacked(r, s, v);

//the signoff only allows alice to call this
vm.startPrank(charlie);
vm.expectRevert(IPNFT.Unauthorized.selector);
ipnft.amendMetadata(1, "ipfs://QmNewUri", authorization);

vm.startPrank(alice);
vm.expectEmit(true, true, false, false);
emit MetadataUpdate(1);
ipnft.amendMetadata(1, "ipfs://QmNewUri", authorization);
assertEq(ipnft.tokenURI(1), "ipfs://QmNewUri");
vm.stopPrank();
}


}