Skip to content

Commit

Permalink
Merge pull request #257 from tablelandnetwork/joe/run-sqls
Browse files Browse the repository at this point in the history
Add an endpoint that can handle multiple writes and/or creates in a single transaction
  • Loading branch information
joewagner authored Mar 31, 2023
2 parents d9dbe00 + 04c5a13 commit b350a36
Show file tree
Hide file tree
Showing 16 changed files with 1,786 additions and 9,183 deletions.
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ coverage*
gasReporterOutput.json
*.d.ts
network.js
hardhat.config.d.ts
scripts/*.d.ts
104 changes: 81 additions & 23 deletions contracts/TablelandTables.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,36 +51,101 @@ contract TablelandTables is
}

/**
* @dev See {ITablelandTables-createTable}.
* @custom:deprecated
* This function is deprecated, please use `create`.
*/
function createTable(
address owner,
string calldata statement
) external payable override whenNotPaused returns (uint256 tableId) {
tableId = _nextTokenId();
_safeMint(owner, 1);
) external payable whenNotPaused returns (uint256) {
return _create(owner, statement);
}

emit CreateTable(owner, tableId, statement);
/**
* @dev See {ITablelandTables-create}.
*/
function create(
address owner,
string calldata statement
) external payable override whenNotPaused returns (uint256) {
return _create(owner, statement);
}

return tableId;
/**
* @dev See {ITablelandTables-create}.
*/
function create(
address owner,
string[] calldata statements
) external payable override whenNotPaused returns (uint256[] memory) {
if (statements.length < 1) revert Unauthorized();

uint256[] memory tableIds = new uint256[](statements.length);
for (uint256 i = 0; i < statements.length; i++) {
tableIds[i] = _create(owner, statements[i]);
}

return tableIds;
}

/**
* @dev See {ITablelandTables-runSQL}.
* @custom:depreciated See {ITablelandTables-runSQL}.
* This function is deprecated, please use `mutate`.
*/
function runSQL(
address caller,
uint256 tableId,
string calldata statement
) external payable whenNotPaused nonReentrant {
_mutate(caller, tableId, statement);
}

/**
* @dev See {ITablelandTables-mutate}.
*/
function mutate(
address caller,
uint256 tableId,
string calldata statement
) external payable override whenNotPaused nonReentrant {
if (!_exists(tableId) || caller != _msgSenderERC721A()) {
revert Unauthorized();
_mutate(caller, tableId, statement);
}

/**
* @dev See {ITablelandTables-mutate}.
*/
function mutate(
address caller,
ITablelandTables.Statement[] calldata statements
) external payable override whenNotPaused nonReentrant {
for (uint256 i = 0; i < statements.length; i++) {
_mutate(caller, statements[i].tableId, statements[i].statement);
}
}

function _create(
address owner,
string calldata statement
) private returns (uint256 tableId) {
tableId = _nextTokenId();
_safeMint(owner, 1);

emit CreateTable(owner, tableId, statement);

return tableId;
}

function _mutate(
address caller,
uint256 tableId,
string calldata statement
) private {
if (!_exists(tableId) || caller != _msgSenderERC721A())
revert Unauthorized();

uint256 querySize = bytes(statement).length;
if (querySize > QUERY_MAX_SIZE) {
if (querySize > QUERY_MAX_SIZE)
revert MaxQuerySizeExceeded(querySize, QUERY_MAX_SIZE);
}

emit RunSQL(
caller,
Expand Down Expand Up @@ -143,9 +208,8 @@ contract TablelandTables is
caller
);
}
if (!(controller == address(0) || controller == caller)) {
if (!(controller == address(0) || controller == caller))
revert Unauthorized();
}

return
TablelandPolicy({
Expand Down Expand Up @@ -177,9 +241,7 @@ contract TablelandTables is
caller != ownerOf(tableId) ||
caller != _msgSenderERC721A() ||
_locks[tableId]
) {
revert Unauthorized();
}
) revert Unauthorized();

_controllers[tableId] = controller;

Expand All @@ -206,9 +268,7 @@ contract TablelandTables is
caller != ownerOf(tableId) ||
caller != _msgSenderERC721A() ||
_locks[tableId]
) {
revert Unauthorized();
}
) revert Unauthorized();

_locks[tableId] = true;
}
Expand Down Expand Up @@ -258,10 +318,8 @@ contract TablelandTables is
uint256 quantity
) internal override {
super._afterTokenTransfers(from, to, startTokenId, quantity);
if (from != address(0)) {
// quantity is only > 1 after bulk minting when from == address(0)
emit TransferTable(from, to, startTokenId);
}
// quantity is only > 1 after bulk minting when from == address(0)
if (from != address(0)) emit TransferTable(from, to, startTokenId);
}

/**
Expand Down
62 changes: 57 additions & 5 deletions contracts/interfaces/ITablelandTables.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,40 +63,92 @@ interface ITablelandTables {
*/
event SetController(uint256 tableId, address controller);

/**
* @dev Struct containing parameters needed to run a mutating sql statement
*
* tableId - the id of the target table
* statement - the SQL statement to run
* - the statement type can be any of INSERT, UPDATE, DELETE, GRANT, REVOKE
*
*/
struct Statement {
uint256 tableId;
string statement;
}

/**
* @dev Creates a new table owned by `owner` using `statement` and returns its `tableId`.
*
* owner - the to-be owner of the new table
* statement - the SQL statement used to create the table
* - the statement type must be CREATE
*
* Requirements:
*
* - contract must be unpaused
*/
function createTable(
function create(
address owner,
string memory statement
) external payable returns (uint256);

/**
* @dev Runs a SQL statement for `caller` using `statement`.
* @dev Creates multiple new tables owned by `owner` using `statements` and returns array of `tableId`s.
*
* owner - the to-be owner of the new table
* statements - the SQL statements used to create the tables
* - each statement type must be CREATE
*
* Requirements:
*
* - contract must be unpaused
*/
function create(
address owner,
string[] calldata statements
) external payable returns (uint256[] memory);

/**
* @dev Runs a mutating SQL statement for `caller` using `statement`.
*
* caller - the address that is running the SQL statement
* tableId - the id of the target table
* statement - the SQL statement to run
* - the statement type can be any of INSERT, UPDATE, DELETE, GRANT, REVOKE
*
* Requirements:
*
* - contract must be unpaused
* - `msg.sender` must be `caller`
* - `tableId` must exist
* - `tableId` must exist and be the table being mutated
* - `caller` must be authorized by the table controller
* - `statement` must be less than or equal to 35000 bytes
*/
function runSQL(
function mutate(
address caller,
uint256 tableId,
string memory statement
string calldata statement
) external payable;

/**
* @dev Runs an array of mutating SQL statements for `caller`
*
* caller - the address that is running the SQL statement
* statements - an array of structs containing the id of the target table and coresponding statement
* - the statement type can be any of INSERT, UPDATE, DELETE, GRANT, REVOKE
*
* Requirements:
*
* - contract must be unpaused
* - `msg.sender` must be `caller`
* - `tableId` must be the table being muated in each struct's statement
* - `caller` must be authorized by the table controller if the statement is mutating
* - each struct inside `statements` must have a `tableId` that corresponds to table being mutated
* - each struct inside `statements` must have a `statement` that is less than or equal to 35000 bytes after normalization
*/
function mutate(
address caller,
ITablelandTables.Statement[] calldata statements
) external payable;

/**
Expand Down
4 changes: 2 additions & 2 deletions contracts/test/TestCreateFromContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ contract TestCreateFromContract is ERC721, Ownable {
function create(string memory name) public payable {
require(tables[name] == 0, "name already exists");

// Make sure we can get table_id back from calling createTable
uint256 tableId = _tableland.createTable(
// Make sure we can get table_id from the created table
uint256 tableId = _tableland.create(
msg.sender,
string(
abi.encodePacked(
Expand Down
47 changes: 47 additions & 0 deletions contracts/test/TestReentrancyMutate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import {ITablelandTables} from "../interfaces/ITablelandTables.sol";
import {TablelandController} from "../TablelandController.sol";
import {Policies} from "../policies/Policies.sol";
import {TablelandPolicy} from "../TablelandPolicy.sol";
import "../policies/ERC721EnumerablePolicies.sol";
import "../policies/ERC721AQueryablePolicies.sol";

contract TestReentrancyMutate is TablelandController, ERC721, Ownable {
ITablelandTables private _tableland;
ITablelandTables.Statement[] private statements;

constructor(address registry) ERC721("TestCreateFromContract", "MTK") {
_tableland = ITablelandTables(registry);
}

function getPolicy(
address,
uint256
) public payable override returns (TablelandPolicy memory) {
// try to reenter `mutate` with some kind of malicious call...
uint256 tableId = 1;
ITablelandTables.Statement memory statement = ITablelandTables
.Statement({
tableId: tableId,
statement: "delete * from msgsendertableidontown"
});

statements.push(statement);

_tableland.mutate(msg.sender, statements);
// Return allow-all policy
return
TablelandPolicy({
allowInsert: true,
allowUpdate: true,
allowDelete: true,
whereClause: Policies.joinClauses(new string[](0)),
withCheck: Policies.joinClauses(new string[](0)),
updatableColumns: new string[](0)
});
}
}
39 changes: 39 additions & 0 deletions contracts/test/TestReentrancyMutateOne.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ITablelandTables} from "../interfaces/ITablelandTables.sol";
import {TablelandController} from "../TablelandController.sol";
import {Policies} from "../policies/Policies.sol";
import {TablelandPolicy} from "../TablelandPolicy.sol";

contract TestReentrancyMutateOne is TablelandController, ERC721, Ownable {
ITablelandTables private _tableland;

constructor(address registry) ERC721("TestCreateFromContract", "MTK") {
_tableland = ITablelandTables(registry);
}

function getPolicy(
address,
uint256
) public payable override returns (TablelandPolicy memory) {
uint256 tableId = 1;
_tableland.mutate(
msg.sender,
tableId,
"delete * from msgsendertableidontown"
);
// Return allow-all policy
return
TablelandPolicy({
allowInsert: true,
allowUpdate: true,
allowDelete: true,
whereClause: Policies.joinClauses(new string[](0)),
withCheck: Policies.joinClauses(new string[](0)),
updatableColumns: new string[](0)
});
}
}
Loading

0 comments on commit b350a36

Please sign in to comment.