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

Contract Module to support ERC5169 (TokenScript) #4869

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/smooth-glasses-invite.md
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

Added contract module to support ERC5169, extended SupportsInterface.behavior.js with new interface
62 changes: 62 additions & 0 deletions contracts/token/common/ERC5169.sol
@@ -0,0 +1,62 @@
// Smart Token Labs 2022 - 2024
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {ERC165} from "../../utils/introspection/ERC165.sol";
import {IERC5169} from "./IERC5169.sol";

/**
* Tokenscript is a framework designed to make contract interaction easy.
* https://www.tokenscript.org/docs/TokenScript-Component.html
* A popular example of TokenScript usage is https://www.smartlayer.network/smartcat
* Users can use TokenScript viewer to open their own SmartCat NFTs and interact with the contract
* with TokenScript UI. UI and code embedded in the TokenScript file, thereby eliminating
* the need to create/host webpage or app to help user interact with the contract.
* TokenScript can automate most of contract interactions with and require a bit
* of coding on Svelte/React/VanillaJS
*/
abstract contract ERC5169 is IERC5169, ERC165 {
/**
* @dev List of TokenScript files locations, related to this contract.
*/
string[] private _scriptURI;

/**
* @dev Return all TokenScriptURIs for the contract
*/
function scriptURI() external view override returns (string[] memory) {
return _scriptURI;
}

/**
@dev Set/replace TokenScriptURIs
*/
function setScriptURI(string[] memory newScriptURI) external override {
_authorizeSetScripts(newScriptURI);

_scriptURI = newScriptURI;

emit ScriptUpdate(newScriptURI);
}

/**
* @dev Required to let other contracts/services detect support of ERC5169
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC5169).interfaceId || super.supportsInterface(interfaceId);
}

/**
* @dev Function that should revert when `msg.sender` is not authorized to set script URI. Called by
* {setScriptURI}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* Implementation example for Ownable contract:
* ```solidity
* function _authorizeSetScripts(string[] memory) internal override onlyOwner {}
* ```
*/
function _authorizeSetScripts(string[] memory newScriptURI) internal virtual;
}
36 changes: 36 additions & 0 deletions contracts/token/common/IERC5169.sol
@@ -0,0 +1,36 @@
// Smart Token Labs 2022 - 2024
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

/**
* https://www.tokenscript.org/
* Standard for bridging Web2 and Web3
*
* TokenScript is a JavaScript/WebAssembly/XML framework to improve functionality,
* security and usability of blockchain tokens in a myriad of ways.
* It allows token issuers and other trusted authorities to enrich a given token
* with a wide set of information, rules and functionalities.
* With TokenScript, wallets and web services can easily, securely and privately
* embed a token with all its functions, both on-chain and off-chain,
* without the need to understand the underlying smart contract.
*
* ERC5169 required to attach script to a Smart Contract and then user can use
* user frindly UI to interact with Smart Contract and Web2 services
*/
interface IERC5169 {
/**
* @dev Have to emit when scriptURIs updated
*/
event ScriptUpdate(string[]);

/**
* @dev Get all scriptURIs for the contract
*/
function scriptURI() external view returns (string[] memory);

/**
@dev Replace scriptURIs with new ones
*/
function setScriptURI(string[] memory) external;
}
22 changes: 22 additions & 0 deletions test/token/common/ERC5169.behavior.js
@@ -0,0 +1,22 @@
const { expect } = require('chai');

const { shouldSupportInterfaces } = require('../../utils/introspection/SupportsInterface.behavior');

function shouldBehaveLikeERC5169() {
shouldSupportInterfaces(['ERC5169']);
shouldSupportInterfaces(['0xa86517a1']);

describe('default', function () {
it('setScriptURI/scriptURI', async function () {
expect(await this.token.scriptURI()).to.deep.equal([]);

const scripts = ['script1', 'script2'];
await expect(this.token.setScriptURI(scripts)).to.emit(this.token, 'ScriptUpdate').withArgs(scripts);
expect(await this.token.scriptURI()).to.deep.equal(scripts);
});
});
}

module.exports = {
shouldBehaveLikeERC5169,
};
1 change: 1 addition & 0 deletions test/utils/introspection/SupportsInterface.behavior.js
Expand Up @@ -80,6 +80,7 @@ const SIGNATURES = {
'castVoteWithReasonAndParamsBySig(uint256,uint8,address,string,bytes,bytes)',
],
ERC2981: ['royaltyInfo(uint256,uint256)'],
ERC5169: ['scriptURI()', 'setScriptURI(string[])'],
};

const INTERFACE_IDS = mapValues(SIGNATURES, interfaceId);
Expand Down