上一章节我们已经,部署了Truora,并且访问正常,这一章节我们开始编写我们的核心的合约,并部署连接上Truora服务。
上一章节我们已经,部署了Truora,并且访问正常,这一章节我们开始编写我们的核心的合约,并部署连接上Truora服务。
这里我们可以参照官方的步骤:https://truora.readthedocs.io/zh_CN/latest/docs/develop/quick-start.html#id1
打开一键部署的 WeBASE-Front 页面,默认:http://{IP}:5002/WeBASE-Front/,使用部署主机的 IP 地址替换 {IP}。 点击左边 合约管理 –> 测试用户,创建一个调试用户 larry
点击左边 合约管理 –> 合约 IDE,选择 solidity 版本,上传模板合约,包括以下五个合约:
FiscoOracleClient.sol
pragma solidity ^0.6.0;
import "./SafeMath.sol";
import "./OracleCoreInterface.sol";
abstract contract FiscoOracleClient {
using SafeMath for uint256;
OracleCoreInterface private oracle;
uint256 private requestCount = 1;
mapping(bytes32 => address) private pendingRequests;
mapping (address => uint) private reqc;
uint256 constant public EXPIRY_TIME = 10 * 60 * 1000;
event Requested(bytes32 indexed id);
event Fulfilled(bytes32 indexed id);
function __callback(bytes32 requestId, int256 result) public virtual;
// __callback with proof
// function __callback(bytes32 requestId, int256 result, bytes calldata proof) public virtual;
function oracleQuery(address _oracle, string memory url, uint256 timesAmount)
internal
returns (bytes32 requestId)
{
return oracleQuery(EXPIRY_TIME,"url", _oracle, url, timesAmount, false);
}
function oracleQuery(uint expiryTime, string memory datasource, address _oracle, string memory url, uint256 timesAmount, bool needProof) internal
returns (bytes32 requestId) {
// calculate the id;
oracle = OracleCoreInterface(_oracle);
int256 chainId;
int256 groupId;
( chainId, groupId) = oracle.getChainIdAndGroupId();
requestId = keccak256(abi.encodePacked(chainId, groupId, this, requestCount));
pendingRequests[requestId] = _oracle;
emit Requested(requestId);
require(oracle.query(address(this),requestCount, url,timesAmount, expiryTime,needProof),"oracle-core invoke failed!");
requestCount++;
reqc[msg.sender]++;
return requestId;
}
/**
* @notice Sets the stored oracle core address
* @param _oracle The address of the oracle core contract
*/
function setOracleCoreAddress(address _oracle) internal {
oracle = OracleCoreInterface(_oracle);
}
/**
* @notice Retrieves the stored address of the oracle contract
* @return The address of the oracle contract
*/
function getOracleCoreAddress()
internal
view
returns (address)
{
return address(oracle);
}
/**
* @dev Reverts if the sender is not the oracle of the request.
* @param _requestId The request ID for fulfillment
*/
modifier onlyOracleCoreInvoke(bytes32 _requestId) {
require(msg.sender == pendingRequests[_requestId],
"Source must be the oracle of the request");
delete pendingRequests[_requestId];
emit Fulfilled(_requestId);
_;
}
}
OracleCore.sol
pragma solidity ^0.6.0;
import "./Ownable.sol";
import "./SafeMath.sol";
/**
* @title The contract for oracle service listening
*/
contract OracleCore is Ownable {
using SafeMath for uint256;
mapping(bytes32 => bytes32) private commitments;
mapping(bytes32 => uint256) timeoutMap;
int256 private chainId;
int256 private groupId;
bytes4 private callbackFunctionId = bytes4(keccak256("__callback(bytes32,int256)"));
event OracleRequest(
address callbackAddr,
bytes32 requestId,
string url,
uint256 expiration,
uint256 timesAmount,
bool needProof
);
constructor(int256 _chainId, int256 _groupId) public Ownable()
{
chainId = _chainId;
groupId = _groupId;
}
function query(
address _callbackAddress,
uint256 _nonce,
string calldata _url,
uint256 _timesAmount,
uint256 _expiryTime,
bool _needProof
)
external
returns(bool)
{
bytes32 requestId = keccak256(abi.encodePacked(chainId, groupId, _callbackAddress, _nonce));
require(commitments[requestId] == 0, "Must use a unique ID");
uint256 expiration = now.add(_expiryTime);
timeoutMap[requestId] = expiration;
commitments[requestId] = keccak256(
abi.encodePacked(
_callbackAddress,
expiration
)
);
emit OracleRequest(
_callbackAddress,
requestId,
_url,
expiration,
_timesAmount,
_needProof);
return true;
}
function fulfillRequest(
bytes32 _requestId,
address _callbackAddress,
uint256 _expiration,
uint256 _result,
bytes calldata proof
)
public
onlyOwner
isValidRequest(_requestId)
returns (bool)
{
bytes32 paramsHash = keccak256(
abi.encodePacked(
_callbackAddress,
_expiration
)
);
require(commitments[_requestId] == paramsHash, "Params do not match request ID");
delete commitments[_requestId];
delete timeoutMap[_requestId];
(bool success, ) = _callbackAddress.call(abi.encodeWithSelector(callbackFunctionId, _requestId, _result)); // solhint-disable-line avoid-low-level-calls
return success;
}
function getChainIdAndGroupId() public view returns(int256,int256){
return (chainId, groupId);
}
/**
* @dev Reverts if request ID does not exist or time out.
* @param _requestId The given request ID to check in stored `commitments`
*/
modifier isValidRequest(bytes32 _requestId) {
require(commitments[_requestId] != 0, "Must have a valid requestId");
require(timeoutMap[_requestId] > now, "fulfill request time out");
_;
}
}
OracleCoreInterface.sol
pragma solidity ^0.6.0;
interface OracleCoreInterface {
function query(
address _callbackAddress,
uint256 _nonce,
string calldata _url,
uint256 _timesAmount,
uint256 _expiryTime,
bool needProof
) external
returns(bool) ;
function getChainIdAndGroupId() external view returns(int256,int256) ;
}
Ownable.sol
pragma solidity ^0.6.0;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be aplied to your functions to restrict their use to
* the owner.
*
* This contract has been modified to remove the revokeOwnership function
*/
contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Returns true if the caller is the current owner.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
SafeMath.sol
pragma solidity ^0.6.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "SafeMath: modulo by zero");
return a % b;
}
}
确认后,选择上传目录,此处选择根目录 /
这里我们就把官方的demo稍微改造一下即可,代码如下: APISampleOracle.sol
pragma solidity ^0.6.0;
import "./FiscoOracleClient.sol";
contract APISampleOracle is FiscoOracleClient {
//指定处理的oracle
address private oracleCoreAddress;
// Multiply the result by 1000000000000000000 to remove decimals
uint256 private timesAmount = 10**18;
mapping(bytes32=>int256) private resultMap;
mapping(bytes32=>bool) private validIds;
int256 public result;
string private url = "plain(http://blog.zhihuixiangxi.com:9999/lottery)";
constructor(address oracleAddress) public {
oracleCoreAddress = oracleAddress;
}
function request() public returns (bytes32)
{
bytes32 requestId = oracleQuery(oracleCoreAddress, url, timesAmount);
validIds[requestId] = true;
return requestId;
}
/**
* Receive the response in the form of int256
*/
function __callback(bytes32 _requestId, int256 _result) public override onlyOracleCoreInvoke(_requestId)
{
require(validIds[_requestId], "id must be not used!") ;
resultMap[_requestId]= _result;
delete validIds[_requestId];
result = _result ;
}
function get() public view returns(int256){
return result;
}
function getById(bytes32 id) public view returns(int256){
return resultMap[id];
}
function checkIdFulfilled(bytes32 id) public view returns(bool){
return validIds[id];
}
function setUrl(string memory _url) public {
url = _url;
}
function getUrl() public view returns(string memory){
return url;
}
}
核心就是 string private url = "plain(http://blog.zhihuixiangxi.com:9999/lottery)"; 和链下的API去交互。API的逻辑我们下一章节详聊。
我们这这里就进入ruora控制台,http://192.168.119.133:5020/#/contractSearch,获取oraclecore合约地址如下图所示:
编译APISampleOracle.sol,点击部署按钮进行部署,填入上一步我们获取的合约地址即可。如下图所示:
我们点击“合约调用”,选择request方法,点击确认如下图所示:
到这里,已经执行了合约并且和链下的API进行了交互。我们也可以再执行get方法进入结果的获取如下图所示: 实际我们这里可以确定我们的链下的API是不会产生小数的,可以合理的去调整合约的逻辑,但是我是选择在java项目代码里面去处理这块逻辑了,如下:
/**
* 执行合约
*
* @param param 合约参数
* @return
*/
public String handle(String param) {
String url = "http://192.168.119.133:5002/WeBASE-Front/trans/handle";
String body = "{\n" +
" \"user\":\"0xf0d04e0cc9b16528207027f1d5020e402096b44e\",\n" +
" \"contractName\":\"APISampleOracle\",\n" +
" \"contractAddress\":\"0x5ffbf18cfbe8c5b8fee09ccde4f5165007a6043e\",\n" +
" \"funcName\":\"" + param + "\",\n" +
" \"contractAbi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"oracleAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"Fulfilled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"Requested\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"EXPIRY_TIME\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_requestId\",\"type\":\"bytes32\"},{\"internalType\":\"int256\",\"name\":\"_result\",\"type\":\"int256\"}],\"name\":\"__callback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"checkIdFulfilled\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"get\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"id\",\"type\":\"bytes32\"}],\"name\":\"getById\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getUrl\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"request\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"result\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"_url\",\"type\":\"string\"}],\"name\":\"setUrl\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}] ,\n" +
" \"groupId\" :\"1\",\n" +
" \"useCns\": false\n" +
"}";
String res = HttpUtil.post(url, body);
if (param.equals("get")) {
// 处理格式
res = res.replace("[", "").replace("]", "").replace("000000000000000000", "");
}
return res;
}
结果也可以在truora控制台的“历史查询”里面去查看,如下图所示:
问题:独立mysql整合到truora如果连接不上,页面不会有任何提示 现象:truora和链下API交互异常,返回0 解决方案:查truora后台的log,路径truora/deploy/log/server/Oracle-Service.log 会包含java.sql.SQLException: Access denied for user 'truora'@'localhost' (using password: YES) 的异常提示 ,在/truora/deploy/docker-compose.yml修正mysql连接参数,重启服务即可。
问题:一键部署后WeBASE-Front创建truora的合约只要重启一下服务就会消失了。
解决方案:
在webase/webase-front.yml配置文件中添加以下配置:
spring:
datasource:
url: jdbc:h2:file:/dist/h2/webasefront;DB_CLOSE_ON_EXIT=FALSE
然后重启一下服务即可解决。
PS:上述问题官方会在下一个版本进行修复
Truora第三篇系列文章我们重点讲解《合约的开发与部署》,注意闭坑指南。其他的按照教程一步一步来成功率99%以上。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!