tablelandnetwork / evm-tableland Goto Github PK
View Code? Open in Web Editor NEWTableland Smart Contracts - Simple solidity tooling to interact with Tableland from on-chain calls.
Home Page: https://tableland.xyz
License: MIT License
Tableland Smart Contracts - Simple solidity tooling to interact with Tableland from on-chain calls.
Home Page: https://tableland.xyz
License: MIT License
Describe the bug
Right now, we check for statement size in our smart contract to ensure all statements are under the our network 35kb limit. This check actually costs the caller gas, and for very large statements, we will actually run out of gas before we actually perform the check itself. This makes it essentially wasteful and not particularly useful. And when we make a call where the checks will pass, then it is again wasteful because we essentially already know it will pass.
To Reproduce
I'm going to tag @joewagner here for more context from his tests.
Expected behavior
We should just perform these checks in a standard way on clients, and in the validators, but not in the contract where it costs gas.
About:
Tableland Smart Contracts - Effortlessly interact with Tableland using powerful on-chain components.
Intro:
Tableland Smart Contracts enable seamless interaction with the Tableland protocol through on-chain components like Registry and Controller contracts. With fine-grained access control and utility contracts for easy setup and SQL statement formation, developers can efficiently create, manage, and interact with tables on the decentralized network.
From SyncLinear.com | EVM-42
Originally posted by sanderpick August 19, 2022
Tension: https://github.com/orgs/tablelandnetwork/discussions/230
We want to let a single txn touch multiple tables. Here's a proposal for altering TablelandTables
to enable this.
Create an overloaded version of runSQL
that takes an array of structs containing a table ID and an array of SQL statements. Pseudo-ish code:
struct Runnable {
uint256 tableId;
string[] statements;
}
function runSQL(
address caller,
Runnable[] memory runs,
) external payable;
I also like the idea of including table creation in a single endpoint like this. We could say that if the table ID is not defined (has its default value of 0), then interpret it as “create table”.
With a SQL parser available to clients, SDKs could map a plain list of SQL statements to an array of Runnable
(or similarly named object). That is to say, using the API would be abstracted from users in some UX friendly way.
Tableland allows for queries to be appended to the gateway url by first appending query?s=...
to the baseURI
-- this allows for raw SQL queries on the url itself. Then, once the tokenURI
is called, the actual SQL query is appended to the baseURI
to exeucte a query for that specific tokenId
(i.e., https://staging.tableland.network/query?s=select...
at the tokenId
).
To avoid messy smart contract interpolation and presumed url encoding knowledge, there should exist url encoding helpers for smart contracts. Here is an example of what this looks like in the SC itself:
To provide a non-screenshot example, the following is what must be properly encoded with %20
, %3D
, etc. in the SC tokenURI
itself, which returns something like:
https://staging.tableland.network/query?s=select%20*%20from%20rigs_69_3%20where%20id%3D1&mode=object
.
Using the example in the screenshot. instead, support raw SQL in the SC baseURI
such that a url encoding Solidity helper library properly transforms everything to the proper encoding.
A simple approach could be to consider with the following parameters:
"SELECT * FROM",
_metadataTable,
"WHERE id = ",
tokenId
and allow these to be passed as an interpolated string. From what I've researched, there doesn't seem to be a "nice" way to interpolate strings like in JS (SELECT * FROM ${name}...
) or other languages, but that would be ideal. Instead, a more realistic approach would be a library that takes a single string and url encodes it (passing "SELECT * FROM"
converts to "SELECT%20*%20FROM"
) and so on. A sample url encoding reference can be found here.
There could be existing Solidity libraries that support this conversion, but doesn't appear that anyone has put this together (OZ, etc.) based on some brief research.
Example queries encoded in uri -- sharing these to demonstrate the in-SC complexity that may arise come without helpers:
Now that Ethers v6 has been released we should start to work towards using it here so that the SDK can upgrade to v6.
There will be some complexity in upgrading. This package has ethers as a dev dependency, and some of the other dev dependencies mark ethers as a peer dependency. Those peer dependencies all currently have ethers v5 tagged. Looking at the github repos for these packages makes it seem like they are all going to have versions that use v6 coming out soon.
We should be ready for all those upgrades here.
related:
NomicFoundation/hardhat#3639
tablelandnetwork/tableland-js#41
The issue
Currently, across our SDKs and libraries, we reference the following chain specifiers:
However, these specifiers are specific to Tableland. For instance, ethersjs
(and https://chainlist.org) use the following equivalent set:
staging
suffix?)For localhost, I think hardhat has a convention here, and I think perhaps @joewagner also had an opinion on this one?
Describe the solution you'd like
It would be useful to adopt the above naming conventions in this repo, and in our SDK and CLI tooling, so that a) it is easier for devs to cross over when dealing with chains and networks, and b) so that our code is more directly compatible with ethers and other projects.
Describe alternatives you've considered
At the moment, supporting our own naming conventions require a mapping in the SDK or CLI from Tableland names to external names. This isn't the end of the world, but it does mean that we end up duplicating names across places, rather than having a single source of truth that we can just use "as is".
@carsonfarmer This might be a dumb question, but I'm having trouble figuring out if we are actually using tsconfig.json
somewhere. Is tsconfig.build.json
sufficient?
Digging into commit history it looks like tsconfig.json might have been leftover from the hardhat boilerplate. Do you know/remember if we need that file?
Currently the contract owner can call runSQL, setController, and lockController on behalf of a caller.
This is part of enabling validator relaying. I think the plan going forward is to stop doing validator relays.
some related issues:
#33
tablelandnetwork/go-tableland#360
Describe the bug
CREATE TABLE statements called from the EVN only work when called from the constructor, not from another function.
To Reproduce
_createCanvasTable();
}
function _createCanvasTable() private onlyOwner {
if
block: if(_metadataTableId == 0) {
...}
.Error: cannot estimate gas; transaction may fail or may require manual gas limit [ See: https://links.ethers.org/v5-errors-UNPREDICTABLE_GAS_LIMIT ] (reason="execution reverted", method="estimateGas", transaction={"from":"0xaf8Da163793B628410C82079bBFfDABD7B8f0B25","to":"0x55346c7689D534A37226De1E69866f0117e898cb","data":"0x0f73e33e","accessList":null}, error={"code":-32603,"message":"Internal JSON-RPC error.","data":{"code":3,"message":"execution reverted","data":"0xd1a57ed6"},"stack":"{\n "code": -32603,\n "message": "Internal JSON-RPC error.",\n "data": {\n "code": 3,\n "message": "execution reverted",\n "data": "0xd1a57ed6"\n },\n "stack": "Error: Internal JSON-RPC error.\n at new i (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-2.js:1:308657)\n at a (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-2.js:1:311413)\n at Object.internal (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-2.js:1:312023)\n at c (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-3.js:3:101606)\n at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-3.js:3:102638\n at async chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:3412"\n}\n at new i (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-2.js:1:308657)\n at a (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-2.js:1:311413)\n at Object.internal (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-2.js:1:312023)\n at c (chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-3.js:3:101606)\n at chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/background-3.js:3:102638\n at async chrome-extension://nkbihfbeogaeaoehlefnkodbefgpgknn/common-6.js:1:3412"}, code=UNPREDICTABLE_GAS_LIMIT, version=providers/5.6.8)
at f.makeError (index.js:219:23)
at f.throwError (index.js:228:20)
at Ri (json-rpc-provider.js:76:20)
at Vi. (json-rpc-provider.js:533:24)
at Generator.throw ()
at a (json-rpc-provider.js:6:64)
initiateSetupSubmission @ setup.js:172
await in initiateSetupSubmission (async)
(anonymous) @ common.js:205
If you're making this call from a browser with MetaMask, you won't even get the MetaMask transaction confirmation popup.
17. Manually add a gas limit to the transaction call. You can pick something very high, like 15 million (the block limit is 20M; 10M goes faster).
18. Now, you'll get a confirmation popup and can send the transaction (example: 0xf9bbdba3e4a5561e1454e14772e4f1efbbacd4f83e7d6f3fa1ddb69c89419cc4) but it'll fail. I'm not able to discern why at present.
Expected behavior
The table-creation call at the end succeeds.
TableLand's documented limit of one table creation per transaction means that DApps requiring multiple tables (per TableLand recommendation to better leverage the power of relational structures) will have to be created in onlyOwner post-deploy calls like this.
Desktop (please complete the following information):
Additional context
New user here; maybe it's just a matter of improving documentation to show how this can be as easy as claimed?
Originally posted by sanderpick August 19, 2022
Tension: https://github.com/orgs/tablelandnetwork/discussions/230
We want to let a single txn touch multiple tables. Here's a proposal for altering TablelandTables
to enable this.
Create an overloaded version of runSQL
that takes an array of structs containing a table ID and an array of SQL statements. Pseudo-ish code:
struct Runnable {
uint256 tableId;
string[] statements;
}
function runSQL(
address caller,
Runnable[] memory runs,
) external payable;
I also like the idea of including table creation in a single endpoint like this. We could say that if the table ID is not defined (has its default value of 0), then interpret it as “create table”.
With a SQL parser available to clients, SDKs could map a plain list of SQL statements to an array of Runnable
(or similarly named object). That is to say, using the API would be abstracted from users in some UX friendly way.
From SyncLinear.com | EVM-26
This is a bit tricky. But right now the Controller method looks like,
interface ITablelandController {
/**
* @dev Object defining how a table can be accessed.
*/
struct Policy {
bool allowInsert;
bool allowUpdate;
bool allowDelete;
string whereClause;
string withCheck;
string[] updatableColumns;
}
/**
* @dev Returns a {Policy} struct defining how a table can be accessed by `caller`.
*/
function getPolicy(address caller) external payable returns (Policy memory);
}
This presents a problem for smart contracts that provide the controller for multiple tables. They all have to have the same policy, or you need different contracts for every policy. This is a new problem for contracts that generate tables dynamically and then want to assign ACL to those tables.
Instead, something like this would be great,
interface ITablelandController {
/**
* @dev Object defining how a table can be accessed.
*/
struct Policy {
bool allowInsert;
bool allowUpdate;
bool allowDelete;
string whereClause;
string withCheck;
string[] updatableColumns;
}
/**
* @dev Returns a {Policy} struct defining how a table can be accessed by `caller`.
*/
function getPolicy(address caller, uint256 tokenId) external payable returns (Policy memory);
}
gut check that an insert/update can only modify one table at a time?
If that's true, them the above is a good fix. if it's not true, we'll need to think of arrays or something.
side note, this also ignores prefixes in the getPolicy... just future proofing it to not worry about those.
Is your feature request related to a problem? Please describe.
Not having tableId
in the context of getPolicy
is limiting. See here for how people are working around it: https://github.com/fjij/tableland-deals-protocol/blob/main/blockchain/contracts/ProxyController.sol
I'm also running into this limitation in a contract that owns multiple tables. A contract should be able to be the controller of multiple tables.
Describe the solution you'd like
Having tableId
in getPolicy
would allow the controller contract to switch on its value.
Describe alternatives you've considered
N/a
Additional context
N/a
Within scripts/deploy.ts
we have several commented out deployment configurations. Instead of this we should enable setting the configuration via dotenv or environment variables.
From SyncLinear.com | EVM-38
The createTable
method takes a param called caller
, which is always the table's owner
(i.e., table is minted to this address). This is confusing since the actual caller of this function could be some smart contract that's acting as a relayer (delegatecall) on behalf of some owner
.
Change the param in createTable
from caller
to owner
in TablelandTables.sol
N/A -- but note that other functions use caller
as well, so something to consider if this change goes through
Mentioned in conversation w/ @carsonfarmer
The tests seem to occasionally timeout with the following error:
Deploying new proxy to 'localhost'...
HardhatError: HH108: Cannot connect to the network localhost.
Please make sure your node is running, and check your internet connection and networks config
at HttpProvider._fetchJsonRpcResponse (/home/runner/work/evm-tableland/evm-tableland/node_modules/hardhat/src/internal/core/providers/http.ts:221:15)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async HttpProvider.request (/home/runner/work/evm-tableland/evm-tableland/node_modules/hardhat/src/internal/core/providers/http.ts:85:29)
at async EthersProviderWrapper.send (/home/runner/work/evm-tableland/evm-tableland/node_modules/@nomiclabs/hardhat-ethers/src/internal/ethers-provider-wrapper.ts:13:20)
at async getSigners (/home/runner/work/evm-tableland/evm-tableland/node_modules/@nomiclabs/hardhat-ethers/src/internal/helpers.ts:45:20)
at async main (/home/runner/work/evm-tableland/evm-tableland/scripts/deploy.ts:14:21)
Caused by: Error: connect ECONNREFUSED 127.0.0.1:8545
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1278:16)
Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
Accounts
========
...
https://github.com/tablelandnetwork/evm-tableland/actions/runs/4748702091/jobs/8435195857
From SyncLinear.com | EVM-43
From SyncLinear.com | REVM-12
Something similar to the js-tableland "test:e2e" script that can be used to indicate if changes cause breakage elsewhere in the network/protocol
There is DX friction related to a "complex" table's name prefix_chainId_tableId
as well as managing all tables related to a single application.
For example, there one day could be a table name like this: mytable_1_115792089237316195423570985008687907853269984665640564039457584007913129639935
. It'd be nice if I could abstract that complicated table name away and alias this table with a custom one, similar to an ENS-like feature.
Additionally, with a typical database, I have a .db
/ .sqlite
file that contains a bunch of tables for my app. This would also be a great feature to have -- a namespace that acts as a grouping for a series of table aliases. Both of these ideas can also lead to better table upgradability patterns, enable emulation for DROP
and ALTER
actions, and would work well with (a separate) idea for forking a table's state. E.g., to alter a table means to fork it & create a new table -- the validator copies the source table's data at some block, and the SC updates the alias to point to the new
target table that was minted.
This feature request is focused on the namespace and alias aspects, only.
.db
-like feature for grouping table aliases together.For example, let's say I have a project with tables main_1_1
and attributes_1_2
for my NFT collection called rigs
. With the two points outlined above, I could register some namespace rigs
, and I could alias the tables with something like main
and attributes
that are contained by rigs
. Thus, the final output is rather similar to a typical SQLite setup where rigs.db
holds the aliases main
and attributes
(which point to main_1_1
and attributes_1_2
).
From an implementation perspective, additional work is needed to determine how this could be achieved, such as a new ERC721 vs. 1155 vs. { some other } contract in which the registry reads from. Regardless, a good example to reference would be the ENSRegistry contract.
Not using namespaces / aliases, which is the current state. The DX can be better.
Idea originally described in a TPD here.
Upgrade the contract APIs to support Prepared Statements as defined in the spec.
The "RunSQL" function generates events containing the caller, table ID, and statement, but none of these are indexed. This makes it difficult for users to filter events using provider tools based on these values. Although filtering by the statement is not a common requirement as statements are specific, I think it is a good idea to index the table ID. This way, users can easily find all events related to a particular table without having to manually filter them.
The "RunSQL" function generates events containing the caller, table ID, and statement, but none of these are indexed. This makes it difficult for users to filter events using provider tools based on these values. Although filtering by the statement is not a common requirement as statements are specific, I think it is a good idea to index the table ID. This way, users can easily find all events related to a particular table without having to manually filter them.
From SyncLinear.com | EVM-18
From SyncLinear.com | REVM-10
Originally posted by sanderpick August 19, 2022
This is a tough one. I love the simplicity of GRANT
and REVOKE
which leverage the validator's notion of "controller", and the power of the smart contract based "controller". But it would be great to unify these concepts somehow.
I know at one time we considered enabling GRANT
and REVOKE
in the registry. We could leverage the WASM SQL parser in clients and simply map those statements to contract methods, eg, GRANT
-> TablelandTables.grant(...)
. However, this approach on its own doesn't allow for granular control over the type of writes, ie, GRANT INSERT|UPDATE|DELETE
, since there is no way to enforce that in the contract w/o a solidity SQL parser.
We could extend the registry with tableId
-> role mappings for insert
, update
, delete
. This state would alter the Policy
s that are emitted. For example, imagine a table has a controller contract that returns a Policy
. If an address is granted the insert
role, the registry would alter it with allowInsert
-> true
. The rest of the policy could be driven by dynamic logic as usual. This becomes pretty flexible, e.g., you could have a policy that only let's holders of some token to write, but dynamically add / remove other valid addresses, etc. ("all meebit holders can write... plus bob", where adding bob doesn't require updating the contract.
With this approach, roles would also be unified with the concept of locking a controller.
On the other hand, managing roles means adding state, which means more expensive GRANT
s.
From SyncLinear.com | EVM-22
Now that Ethers v6 has been released we should start to work towards using it here so that the SDK can upgrade to v6.
There will be some complexity in upgrading. This package has ethers as a dev dependency, and some of the other dev dependencies mark ethers as a peer dependency. Those peer dependencies all currently have ethers v5 tagged. Looking at the github repos for these packages makes it seem like they are all going to have versions that use v6 coming out soon.
We should be ready for all those upgrades here.
related:
NomicFoundation/hardhat#3639
tablelandnetwork/tableland-js#41
From SyncLinear.com | EVM-15
I'm going to look into this more, but building the SDK does not seem to work anymore. As far as I can tell it's an issue with "@typechain/ethers-v5" that came out of changes moving from "10.0.0" to "10.1.0".
There's an issue open here: dethcrypto/TypeChain#721 but not much conversation.
If we pin at "10.0.0" other dependencies have a conflict and we get warnings ala npm WARN ERESOLVE overriding peer dependency
.
We need a way to declaratively assign policies to tables to enable table configurations. This work should be done in conjunction with the table configuration work (todo: link that issue to this one).
From SyncLinear.com | EVM-2
From SyncLinear.com | EVM-39
We need a way to declaratively assign policies to tables to enable table configurations. This work should be done in conjunction with the table configuration work (todo: link that issue to this one).
From SyncLinear.com | REVM-2
Starting this week the publish action fails with the error
Downloading compiler 0.8.19
Error: scripts/deploy.ts(3,38): error TS[23](https://github.com/tablelandnetwork/evm-tableland/actions/runs/4821511960/jobs/8587480590#step:9:24)07: Cannot find module '../typechain-types' or its corresponding type declarations.
Error: scripts/upgrade.ts(3,38): error TS2307: Cannot find module '../typechain-types' or its corresponding type declarations.
npm ERR! code 2
npm ERR! path /home/runner/work/evm-tableland/evm-tableland
npm ERR! command failed
npm ERR! command sh -c npm run build
npm ERR! A complete log of this run can be found in:
npm ERR! /home/runner/.npm/_logs/2023-04-27T15_20_20_930Z-debug-0.log
Error: Error: Unable to publish @tableland/evm v4.2.1 to NPM.
npm publish --access public exited with a status of 2.
at Object.publish (/home/runner/work/_actions/JS-DevTools/npm-publish/v1/src/npm.ts:112:13)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
ProcessError: npm publish --access public exited with a status of 2.
at normalizeResult (/home/runner/work/_actions/JS-DevTools/npm-publish/v1/node_modules/@jsdevtools/ez-spawn/lib/normalize-result.js:31:1)
at ChildProcess.<anonymous> (/home/runner/work/_actions/JS-DevTools/npm-publish/v1/node_modules/@jsdevtools/ez-spawn/lib/async.js:79:1)
at ChildProcess.emit (events.js:314:20)
at Process.ChildProcess._handle.onexit (internal/child_process.js:[27](https://github.com/tablelandnetwork/evm-tableland/actions/runs/4821511960/jobs/8587480590#step:9:28)6:12)
full failure here: https://github.com/tablelandnetwork/evm-tableland/actions/runs/4821511960/jobs/8587480590
Is your feature request related to a problem? Please describe.
Deploying / upgrading from a single vaulted private key is insecure.
Describe the solution you'd like
We should leverage a gnosis safe multisig flow using the gnosis safe JS lib.
This conditional might be disabling the possibility of using Policy Contract based access control https://github.com/tablelandnetwork/eth-tableland/blob/c44c6bd14c29006e8ac397632a352452f0499846/contracts/TablelandTables.sol#L71
The check reverts with an "Unauthorized" error if the call to _exists()
returns false. As far as I can tell using _burn(tableId)
results in the tableId no longer "existing". Which in turn results in the calls to run sql always failing.
I'm able to burn the table and still access it according to the policy contract rules if I transfer to a burn address like 0x000000000000000000000000000000000000dead
.
There also might be an error in my code that I'm not seeing. To reproduce setup and run the example apps chess game locally.
Describe the bug
I removed a property from the exports object in package.json which will cause problems down stream
b8e8650#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519L19
To Reproduce
The sdk util file uses import * as evm from "@tableland/evm/network.js";
. Since the .js
extension is present, when I removed that from the exports object I broke the sdk but since package-lock was still referencing v3.0.1 of this module the sdk didn't pick up the change yet.
Describe the bug
A clear and concise description of what the bug is.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
A clear and concise description of what you expected to happen.
Screenshots
If applicable, add screenshots to help explain your problem.
Desktop (please complete the following information):
Smartphone (please complete the following information):
Additional context
Add any other context about the problem here.
From SyncLinear.com | REVM-13
From SyncLinear.com | REVM-4
The Tableland methods don't support the notion of an "approved address". I'm not sure we want to, but just flagging it. Marketplaces use this concept to handle sales, auctions, etc.
Do we think it's useful to allow some other address to do things for you? Perhaps the same concept should be used for our concept of "validator proxies", which would let people actually approve / revoke this behavior.
Describe the bug
A clear and concise description of what the bug is.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
A clear and concise description of what you expected to happen.
Screenshots
If applicable, add screenshots to help explain your problem.
Desktop (please complete the following information):
Smartphone (please complete the following information):
Additional context
Add any other context about the problem here.
Use more concrete types in proxies.ts
. Right now it's [key: string]
which means we have to add a new more concrete type in js-tableland
We should be publishing the .sol
contracts so others can import them in their solidity contracts, e.g. import "@tableland/eth/contracts/ITablelandController.sol";
Let's get something going soon that lets us test use cases that require inclusion proofs.
From SyncLinear.com | REVM-16
Is your feature request related to a problem? Please describe.
Not having tableId
in the context of getPolicy
is limiting. See here for how people are working around it: https://github.com/fjij/tableland-deals-protocol/blob/main/blockchain/contracts/ProxyController.sol
I'm also running into this limitation in a contract that owns multiple tables. A contract should be able to be the controller of multiple tables.
Describe the solution you'd like
Having tableId
in getPolicy
would allow the controller contract to switch on its value.
Describe alternatives you've considered
N/a
Additional context
N/a
From SyncLinear.com | EVM-19
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.