🧻
Log Rollup
ログをロールアップすることで、安くL1を使用するアイデア.
例:
- 「太郎くんがaliceに1000eth送る。」というeventをemitする。
- 「aliceは智くんに90ethを送る。」というeventをemitする。
- マイナーは、ログを集計するコントラクトを実行し、JSCODEがハードコードされているChainlinkを動かすコントラクトを実行する。
- ERC20コントラクトの間で、トークンの移動を容易にするコントラクトであるRouterを設置し、それとやりとりすることで、トークンの移動を実現する・
const ethers = await import('npm:ethers@5.7.0');
const API_KEY = secrets.scanApiKey;
const CONTRACT_ADDRESS = '0x6E3c495d83E47e41A6c37A49F89e8f6B459db1E7';
const START_BLOCK = args[0]; // from contract
const provider = new ethers.providers.JsonRpcProvider(secrets.rpc);
const erc20Contract = new ethers.Contract(
CONTRACT_ADDRESS,
['event TransferLog(address indexed from, address indexed to, uint256 value)',
'function setHashForBatch(bytes calldata hash, uint256 lastBlockNumber)'],
provider
);
const wallet = new ethers.Wallet(secrets.privateKey, provider);
const END_BLOCK = await provider.getBlockNumber();
const TOPIC = "0x2083e82b9793082f4e269de9ab01b8b76ee80ee34051ce54d84671158bede8df";
const BASE_URL = 'https://api-sepolia.basescan.org/api';
const response = await Functions.makeHttpRequest({
url: BASE_URL,
method: 'GET',
params: {
module: 'logs',
action: 'getLogs',
fromBlock: START_BLOCK,
toBlock: END_BLOCK,
address: CONTRACT_ADDRESS,
topic0: TOPIC,
apikey: API_KEY
},
responseType: 'json'
});
if (!(response.status === 200 && response.data.status === '1')) {
throw new Error(`Error retrieving logs: ${response.data.message || response.data}`);
}
console.log('Logs retrieved successfully:', response.data.result);
const iface = new ethers.utils.Interface([
'event TransferLog(address indexed from, address indexed to, uint256 value, bytes signature, string message, uint256 nonce)'
]);
const routerTransferFrom = response.data.result
.map(log => iface.parseLog(log).args)
.map(({ from, to, value, signature, message, nonce }) => ({ from, to, value, signature, message, nonce }));
console.log('Parsed event args:', routerTransferFrom);
const routerTransferFromHash = ethers.utils.keccak256(
ethers.utils.defaultAbiCoder.encode(
['tuple(address from, address to, uint256 value, bytes signature, string message, uint256 nonce)[]'],
[routerTransferFrom]
)
);
console.log('Balance Dict Hash:', routerTransferFromHash);
await erc20Contract.setHashForBatch(routerTransferFromHash, END_BLOCK);
return Functions.encodeString(`Balance Dict Hash: ${routerTransferFromHash}`);
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;
import {FunctionsClient} from "@chainlink/contracts@1.1.1/src/v0.8/functions/v1_0_0/FunctionsClient.sol";
import {ConfirmedOwner} from "@chainlink/contracts@1.1.1/src/v0.8/shared/access/ConfirmedOwner.sol";
import {FunctionsRequest} from "@chainlink/contracts@1.1.1/src/v0.8/functions/v1_0_0/libraries/FunctionsRequest.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
interface IRouter {
struct RouterTransfer {
address from;
address to;
uint256 value;
}
function batch(RouterTransfer[] calldata data) external;
function depositAmount(address user) external returns(uint256);
function mining(address miner) external;
}
contract AI is FunctionsClient, ConfirmedOwner {
using FunctionsRequest for FunctionsRequest.Request;
using Strings for uint256;
bytes32 public s_lastRequestId;
bytes public s_lastResponse;
bytes public s_lastError;
// Custom error type
error UnexpectedRequestID(bytes32 requestId);
// Event to log responses
event Response(
bytes32 indexed requestId,
bytes response,
bytes err
);
address router = 0xf9B8fc078197181C841c296C876945aaa425B278;
address tokenRouter;
uint32 gasLimit = 300000;
bytes32 donID =
0x66756e2d626173652d7365706f6c69612d310000000000000000000000000000;
string public source = "const ethers = await import('npm:ethers@5.7.0');const API_KEY = secrets.scanApiKey;const CONTRACT_ADDRESS = '0x6E3c495d83E47e41A6c37A49F89e8f6B459db1E7';const START_BLOCK = args[0];const provider = new ethers.providers.JsonRpcProvider(secrets.rpc);const erc20Contract = new ethers.Contract(CONTRACT_ADDRESS, ['event TransferLog(address indexed from, address indexed to, uint256 value)','function setHashForBatch(bytes calldata hash, uint256 lastBlockNumber)'], provider);const wallet = new ethers.Wallet(secrets.privateKey, provider);const END_BLOCK = await provider.getBlockNumber();const TOPIC = '0x2083e82b9793082f4e269de9ab01b8b76ee80ee34051ce54d84671158bede8df';const BASE_URL = 'https://api-sepolia.basescan.org/api';const response = await Functions.makeHttpRequest({url: BASE_URL,method: 'GET',params: {module: 'logs',action: 'getLogs',fromBlock: START_BLOCK,toBlock: END_BLOCK,address: CONTRACT_ADDRESS,topic0: TOPIC,apikey: API_KEY},responseType: 'json'});if(!(response.status === 200 && response.data.status === '1')){throw new Error(`Error retrieving logs: ${response.data.message || response.data}`);}console.log('Logs retrieved successfully:', response.data.result);const iface = new ethers.utils.Interface(['event TransferLog(address indexed from, address indexed to, uint256 value, bytes signature, string message, uint256 nonce)']);const routerTransferFrom = response.data.result.map(log => iface.parseLog(log).args).map(({ from, to, value, signature, message, nonce }) => ({ from, to, value, signature, message, nonce }));console.log('Parsed event args:', routerTransferFrom);const routerTransferFromHash = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(['tuple(address from, address to, uint256 value, bytes signature, string message, uint256 nonce)[]'], [routerTransferFrom]));console.log('Balance Dict Hash:', routerTransferFromHash);await erc20Contract.setHashForBatch(routerTransferFromHash, END_BLOCK);return Functions.encodeString(`Balance Dict Hash: ${routerTransferFromHash}`);";
uint256 lastBlockNumber;
bytes32 public routerTransferBatchHash;
event TransferLog(address indexed from, address indexed to, uint256 value);
function emitLog(address to, uint256 value) external {
require(IRouter(tokenRouter).depositAmount(msg.sender) >= value, "insufficient");
emit TransferLog(msg.sender, to, value);
}
constructor(address _tokenRouter) FunctionsClient(router) ConfirmedOwner(msg.sender) {
lastBlockNumber = block.timestamp;
tokenRouter = _tokenRouter;
}
function setHashForBatch(uint256 blockNumber, bytes32 hash) external {
lastBlockNumber = blockNumber;
routerTransferBatchHash = hash;
}
function sendBatch(IRouter.RouterTransfer[] calldata routerTransfer) external {
require(keccak256(abi.encode(routerTransfer)) == routerTransferBatchHash, "wrong data");
IRouter(tokenRouter).batch(routerTransfer);
}
function sendRequest(
bytes memory encryptedSecretsUrls,
uint64 subscriptionId
) external returns(bytes32) {
FunctionsRequest.Request memory req;
req.addSecretsReference(encryptedSecretsUrls);
req.initializeRequestForInlineJavaScript(source);
string[] memory args;
args[1] = block.number.toString();
args[2] = lastBlockNumber.toString();
req.setArgs(args);
IRouter(tokenRouter).mining(msg.sender);
s_lastRequestId = _sendRequest(
req.encodeCBOR(),
subscriptionId,
gasLimit,
donID
);
return s_lastRequestId;
}
event Mining(address indexed miner,string indexed miningType, bytes32 indexed requestId, bytes response, bool success);
struct Distribute {
uint256 people;
uint256 industory;
uint256 other;
}
function fulfillRequest(
bytes32 requestId,
bytes memory response,
bytes memory err
) internal override {
if (s_lastRequestId != requestId) {
revert UnexpectedRequestID(requestId);
}
s_lastResponse = response;
s_lastError = err;
emit Response(requestId, s_lastResponse, s_lastError);
}
}
Discussion