Skip to content

Commit

Permalink
sync up 1948 with standard (#213)
Browse files Browse the repository at this point in the history
  • Loading branch information
johannbarbie authored Jun 27, 2019
1 parent 14d2c8b commit 0ea60e4
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 53 deletions.
48 changes: 30 additions & 18 deletions contracts/ERC1948.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,50 @@ pragma solidity ^0.5.2;
import "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol";
import "./IERC1948.sol";

/**
* @dev Implementation of ERC721 token and the `IERC1948` interface.
*
* ERC1948 is a non-fungible token (NFT) extended with the ability to store
* dynamic data. The data is a bytes32 field for each tokenId. If 32 bytes
* do not suffice to store the data, an authenticated data structure (hash or
* merkle tree) shall be used.
*/
contract ERC1948 is IERC1948, ERC721 {

mapping(uint256 => bytes32) data;

function mint(address _to, uint256 _tokenId) public {
super._mint(_to, _tokenId);
function mint(address to, uint256 tokenId) public {
super._mint(to, tokenId);
}

function burn(uint256 _tokenId) public {
super._burn(ownerOf(_tokenId), _tokenId);
delete(data[_tokenId]);
function burn(uint256 tokenId) public {
super._burn(ownerOf(tokenId), tokenId);
delete(data[tokenId]);
}

/**
* @dev Reads the data of a specified token.
* @param _tokenId The token to read the data off.
* @return A bytes32 representing the current data stored in the token.
* @dev See `IERC1948.readData`.
*
* Requirements:
*
* - `tokenId` needs to exist.
*/
function readData(uint256 _tokenId) public view returns (bytes32) {
require(_exists(_tokenId));
return data[_tokenId];
function readData(uint256 tokenId) external view returns (bytes32) {
require(_exists(tokenId));
return data[tokenId];
}

/**
* @dev Updates the data of a specified token.
* @param _tokenId The token to write data to.
* @param _newData The data to be written to the token.
* @dev See `IERC1948.writeData`.
*
* Requirements:
*
* - `msg.sender` needs to be owner of `tokenId`.
*/
function writeData(uint256 _tokenId, bytes32 _newData) public {
require(msg.sender == ownerOf(_tokenId));
emit DataUpdated(_tokenId, data[_tokenId], _newData);
data[_tokenId] = _newData;
function writeData(uint256 tokenId, bytes32 newData) external {
require(msg.sender == ownerOf(tokenId));
emit DataUpdated(tokenId, data[tokenId], newData);
data[tokenId] = newData;
}

}
57 changes: 39 additions & 18 deletions contracts/ERC1949.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,54 @@ pragma solidity 0.5.2;
import "./ERC1948.sol";
import "./IERC1949.sol";


/**
* @dev Implementation of the `IERC1949` interface.
*
* An NFT token contract implementing a delegate authorization model for minting.
* Delegate tokens give minting rights to holders of such token. Delegate token
* holders can mint regular tokens that don't convey minting right.
*
* The delegate authorization model enables deferred mining of tokens, if a delegate
* token is deposited into a side-/child-chain bridge. The rules of minting have to
* be enforced through contracts/predicates on the side-/child-chain and are not part
* of this implementation.
*/
contract ERC1949 is IERC1949, ERC1948 {
uint256 public queenCounter = 0;
mapping(address => uint256) queenOwners;

function mintQueen(address _to) public {
queenCounter += 1;
uint256 queenId = uint256(keccak256(abi.encodePacked(address(this), queenCounter)));
super._mint(_to, queenId);
queenOwners[_to] = queenId;
emit DataUpdated(queenId, data[queenId], bytes32(uint256(1)));
data[queenId] = bytes32(uint256(1));
uint256 public delegateCounter = 0;
mapping(address => uint256) delegateOwners;

// Token name
string public name = "DeferredMinter123";

// Token symbol
string public symbol = "DM1";

/**
* @dev mints a new delegate
* @param _to The token to read the data off.
*/
function mintDelegate(address _to) public {
delegateCounter += 1;
uint256 delegateId = uint256(keccak256(abi.encodePacked(address(this), delegateCounter)));
super._mint(_to, delegateId);
delegateOwners[_to] = delegateId;
emit DataUpdated(delegateId, data[delegateId], bytes32(uint256(1)));
data[delegateId] = bytes32(uint256(1));
}

modifier onlyQueenOwner(address _to) {
modifier onlyDelegateOwner(address to) {
require(
(queenOwners[msg.sender] > 0) ||
((queenOwners[_to] > 0) && _isApprovedOrOwner(msg.sender, queenOwners[_to])),
(delegateOwners[msg.sender] > 0) ||
((delegateOwners[to] > 0) && _isApprovedOrOwner(msg.sender, delegateOwners[to])),
"sender not queen owner nor approved"
);
_;
}

function breed(uint256 _workerId, address _to, bytes32 _workerData) public onlyQueenOwner(_to) {
super._mint(_to, _workerId);
emit DataUpdated(_workerId, data[_workerId], _workerData);
data[_workerId] = _workerData;
function breed(uint256 tokenId, address to, bytes32 tokenData) external onlyDelegateOwner(to) {
super._mint(to, tokenId);
emit DataUpdated(tokenId, data[tokenId], tokenData);
data[tokenId] = tokenData;
}

}
32 changes: 28 additions & 4 deletions contracts/IERC1948.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

/**
* Copyright (c) 2018-present, Leap DAO (leapdao.org)
*
Expand All @@ -7,12 +6,37 @@
*/
pragma solidity ^0.5.2;

contract IERC1948 {
/**
* @dev Interface of the ERC1948 contract.
*/
interface IERC1948 {

/**
* @dev Emitted when `oldData` is replaced with `newData` in storage of `tokenId`.
*
* Note that `oldData` or `newData` may be empty bytes.
*/
event DataUpdated(uint256 indexed tokenId, bytes32 oldData, bytes32 newData);

function readData(uint256 _tokenId) public view returns (bytes32);
/**
* @dev Reads the data of a specified token. Returns the current data in
* storage of `tokenId`.
*
* @param tokenId The token to read the data off.
*
* @return A bytes32 representing the current data stored in the token.
*/
function readData(uint256 tokenId) external view returns (bytes32);

function writeData(uint256 _tokenId, bytes32 _newData) public;
/**
* @dev Updates the data of a specified token. Writes `newData` into storage
* of `tokenId`.
*
* @param tokenId The token to write data to.
* @param newData The data to be written to the token.
*
* Emits a `DataUpdated` event.
*/
function writeData(uint256 tokenId, bytes32 newData) external;

}
11 changes: 9 additions & 2 deletions contracts/IERC1949.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
pragma solidity 0.5.2;

contract IERC1949 {
/**
* @dev Interface of the ERC1949 contract.
*/
interface IERC1949 {

function breed(uint256, address, bytes32) public;
/**
* @dev allows to mint new tokens if `msg.sender` or `to` is owner
* of a delegate token.
*/
function breed(uint256 tokenId, address to, bytes32 tokenData) external;

}
20 changes: 10 additions & 10 deletions test/erc1949.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ contract('ERC1949', (accounts) => {
let queenId;
const creator = accounts[0];
const workerData = '0x0101010101010101010101010101010101010101010101010101010101010101';
let breedToken;
let delegateToken;

beforeEach(async () => {
breedToken = await ERC1949.new();
const rsp = await breedToken.mintQueen(creator);
delegateToken = await ERC1949.new();
const rsp = await delegateToken.mintDelegate(creator);
queenId = rsp.logs[0].args.tokenId;
});

it('should allow breed new worker', async () => {
it('should allow delegate new worker', async () => {
// check queenCounter
const queenCounter = '0x0000000000000000000000000000000000000000000000000000000000000001';
let rsp = await breedToken.readData(queenId);
let rsp = await delegateToken.readData(queenId);
assert.equal(rsp, queenCounter);

// generate workerId
Expand All @@ -29,18 +29,18 @@ contract('ERC1949', (accounts) => {
buffer.writeUInt32BE(1, 60);
const workerId = `0x${keccak256(buffer).toString('hex')}`;

// breed and check result
rsp = await breedToken.breed(workerId, creator, workerData);
// delegate and check result
rsp = await delegateToken.breed(workerId, creator, workerData);
const mintedId = `0x${rsp.logs[1].args.tokenId.toString('hex')}`;
assert.equal(workerId, mintedId);

// check worker data
const data = await breedToken.readData(workerId);
const data = await delegateToken.readData(workerId);
assert.equal(data, workerData);
});

it('should fail if breed called by non-owner', async () => {
await breedToken.breed(123, creator, workerData, {from: accounts[1]}).should.be.rejectedWith(EVMRevert);
it('should fail if delegate called by non-owner', async () => {
await delegateToken.breed(123, creator, workerData, {from: accounts[1]}).should.be.rejectedWith(EVMRevert);
});

});
2 changes: 1 addition & 1 deletion test/exitHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ contract('ExitHandler', (accounts) => {
it('Should allow to finalize delayed breeder', async () => {
// deposit queen
const breedToken = await ERC1949.new();
await breedToken.mintQueen(exitHandler.address);
await breedToken.mintDelegate(exitHandler.address);

// register token
const data = await exitHandler.contract.methods.registerNST(breedToken.address).encodeABI();
Expand Down

0 comments on commit 0ea60e4

Please sign in to comment.