Skip to content
Closed
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
64 changes: 64 additions & 0 deletions contracts/ERC1238/extensions/ERC1238Holdable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../ERC1238.sol";
import "./IERC1238Holdable.sol";

/**
* @dev Proposal for ERC1238 tokens extension that allow addresses
* to hold tokens on behalf of others (escrow)
*/
abstract contract ERC1238Holdable is IERC1238Holdable, ERC1238 {
// Mapping holder => id => balance
mapping(address => mapping(uint256 => uint256)) private _escrowedBalances;

function escrowedBalance(address holder, uint256 id) public view override returns (uint256) {
return _escrowedBalances[holder][id];
}

function _beforeMint(
address minter,
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual override {
// set the token recipient as first holder by default when tokens are minted
_escrowedBalances[to][id] += amount;
}

function _beforeBurn(
address burner,
address from,
uint256 id,
uint256 amount
) internal virtual override {
require(_escrowedBalances[burner][id] >= amount, "ERC1238Holdable: Amount to burn exceeds amount held");

_escrowedBalances[burner][id] -= amount;
}

/**
* @dev Lets sender entrusts `to` with `amount`
* of tokens which gets transferred between their respective escrowedBalances
*
*/
function _entrust(
address to,
uint256 id,
uint256 amount
) internal virtual {
require(to != address(0), "ERC1238Holdable: transfer to the zero address");

address from = msg.sender;

uint256 fromBalance = _escrowedBalances[from][id];
require(fromBalance >= amount, "ERC1238Holdable: amount exceeds balance held");

_escrowedBalances[from][id] -= amount;
_escrowedBalances[to][id] += amount;

emit Entrust(from, to, id, amount);
}
}
85 changes: 85 additions & 0 deletions contracts/ERC1238/extensions/ERC1238Stakable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../ERC1238.sol";

/**
* @dev Proposal for ERC1238 tokens extension that make them 'stakable'
*/
abstract contract ERC1238Stakable is ERC1238 {
// Mapping owner => token id => stakeholder => stake size
mapping(address => mapping(uint256 => mapping(address => uint256))) private _stakes;

function stakeOf(
address owner,
uint256 id,
address stakeholder
) public view returns (uint256) {
return _stakes[owner][id][stakeholder];
}

/**
* @dev Called before tokens are burned
*
* Requirements:
* - `burner` and `from` are the same account OR
* - `from` entrusted `burner` with at least `amount` of tokens with id `id`
*/
function _beforeBurn(
address burner,
address from,
uint256 id,
uint256 amount
) internal virtual override {
if (burner != from) {
require(_stakes[from][id][burner] >= amount, "ERC1238Stakable: Unauthorized to burn tokens");
_decreaseStakeFrom(burner, from, id, amount);
}
}

/**
* @dev Allows token owners to put their tokens at stake
*
* Calling this function again with the same stakeholder and id
* adds to the previous staked amount
*
*/
function _increaseStake(
address stakeholder,
uint256 id,
uint256 amount
) internal {
_stakes[msg.sender][id][stakeholder] += amount;
}

/**
* @dev Lets sender (stakeholder) decrease a staked `amount` of
* tokens with id `id` belonging to `owner`
*
* Requirements:
*
* - `amount` must be less that the current staked amount
*/
function _decreaseStake(
address owner,
uint256 id,
uint256 amount
) internal {
_decreaseStakeFrom(msg.sender, owner, id, amount);
}

function _decreaseStakeFrom(
address stakeholder,
address owner,
uint256 id,
uint256 amount
) private {
uint256 authorization = _stakes[owner][id][stakeholder];

require(authorization >= amount, "ERC1238Stakable: cannot decrease more than current stake");
unchecked {
_stakes[owner][id][stakeholder] = authorization - amount;
}
}
}
26 changes: 26 additions & 0 deletions contracts/ERC1238/extensions/IERC1238Holdable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../IERC1238.sol";

/**
* @dev Proposal of an interface for ERC1238 token with storage based token URI management.
*/
interface IERC1238Holdable is IERC1238 {
/**
* @dev Event emitted when `from` entrusts `to` with `amount` of tokens with token `id`
*/
event Entrust(address from, address to, uint256 indexed id, uint256 amount);

/**
* @dev Returns the balance of a token holder for a given `id`
*/
function escrowedBalance(address holder, uint256 id) external view returns (uint256);

function entrust(
address to,
uint256 id,
uint256 amount
) external;
}
74 changes: 74 additions & 0 deletions contracts/mocks/ERC1238HoldableMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../ERC1238/ERC1238.sol";
import "../ERC1238/extensions/ERC1238Holdable.sol";

/**
* @dev Mock contract for ERC1238 tokens using ERC1238Holdable extension
*/
contract ERC1238HoldableMock is ERC1238, ERC1238Holdable {
constructor(string memory uri) ERC1238(uri) {}

function _beforeMint(
address minter,
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal override(ERC1238, ERC1238Holdable) {
super._beforeMint(minter, to, id, amount, data);
}

function _beforeBurn(
address burner,
address from,
uint256 id,
uint256 amount
) internal override(ERC1238, ERC1238Holdable) {
super._beforeBurn(burner, from, id, amount);
}

function mint(
address to,
uint256 id,
uint256 amount,
bytes memory data
) public {
_mint(to, id, amount, data);
}

function mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public {
_mintBatch(to, ids, amounts, data);
}

function burn(
address owner,
uint256 id,
uint256 amount
) public {
_burn(owner, id, amount);
}

function burnBatch(
address owner,
uint256[] memory ids,
uint256[] memory amounts
) public {
_burnBatch(owner, ids, amounts);
}

function entrust(
address to,
uint256 id,
uint256 amount
) public override {
_entrust(to, id, amount);
}
}
55 changes: 55 additions & 0 deletions contracts/mocks/ERC1238StakableMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../ERC1238/ERC1238.sol";
import "../ERC1238/extensions/ERC1238Stakable.sol";

/**
* @dev Mock contract for ERC1238 tokens using ERC1238Stakable extension rendering them 'stakable'
*/
contract ERC1238StakableMock is ERC1238, ERC1238Stakable {
constructor(string memory uri) ERC1238(uri) {}

function _beforeBurn(
address burner,
address from,
uint256 id,
uint256 amount
) internal override(ERC1238, ERC1238Stakable) {
super._beforeBurn(burner, from, id, amount);
}

function mint(
address to,
uint256 id,
uint256 amount,
bytes memory data
) public {
_mint(to, id, amount, data);
}

function burn(
address owner,
uint256 id,
uint256 amount
) public {
_burn(owner, id, amount);
}

function increaseStake(
address stakeholder,
uint256 id,
uint256 amount
) external {
_increaseStake(stakeholder, id, amount);
}

function decreaseStake(
address owner,
uint256 id,
uint256 amount
) external {
_decreaseStake(owner, id, amount);
}
}
Loading