-
Notifications
You must be signed in to change notification settings - Fork 0
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
SmartContract #28
Comments
for mind sake,i choose description in Chinese. 想要了解智能合约,就需要了解每一种链的关于账户的机制(比特币账户机制,以太坊的账户机制,跟Solana各不一样),当然共同概念也非常的多,如执行引擎。 以太坊账户类别:
交易,唯一触发合约执行;交易可以形成调用链条(交易A -->触发合约B,合约代码生成交易C--> 触发合约D,--->...),起始交易的触发一定是从EOA账户发起的交易,另外以太坊中把合约生成的交易叫message call(消息调用),只是叫法不同,消息本质也是交易,也要消耗gas费。 CA/合约账户,data区域对应着合约的状态,如下的合约代码: // SPDX-License-Idenetifier: MIT //license声明
pragma solidity ^0.8.0; //指定solidity支持版本号
contract SimpleStorage { //合约名,CamelCase风格
uint storeData; //变量名, camelCase风格
// function 函数名(参数类型 参数名) modifier
function set(uint x) public {
storeData = x;
}
// function 函数名(参数类型 参数名) modifier returns (返回值类型)
function get() constant returns (uint) {
return storedData;
}
} 其中用户调用过set函数后,storeData就存在于CA账户的data数据中,对应着合约的状态。 上面函数的modifier有好几类,public/private/...是可见性invisibility,constant是mutablity,... Solidity合约编程有官方style 后续不断更新一些小section |
简单token铸造和派发: send(receiver, amount) // SPDX-License-Idenetifier: MIT
pragma solidity ^0.8.0;
contract Token {
address public minter;
mapping (address => uint) public balances;
event Sent(address from, address to, uint amount);
constructor() public {
minter = msg.sender;
}
function mint(uint amount) public {
if (msg.sender != minter) return;
balances[minter] += amount;
}
function send(address receiver, uint amount) public {
if (balances[minter] < amount) return;
balances[minter] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount)
}
} notes:
|
以太坊账户结构:无论是CA账户还是EOA账户,如下数据域都存在:
codeHash对于CA账户有意义,合约账户的合约代码,都有Kecca256 Hash值,在EVM处,代表该合约;也就是说,EOA账户调用了合约A,即指定了合约的codeHash,在EVM处,由指定的codeHash即可找到合约A。 storageRoot对于CA账户,即代表了账户代码中各种变量的保存,比如上说上面代码,Token合约中的minter和balances,在底层是以key->value实行存储。 EOA账户创建不需要发交易,不会消耗gas;但CA账户创建要发交易(to address为特定的0x0),也要消耗gas 以太坊的
|
overflow, underflow and unchecked blockSolidity中integer类型有:unsigned和signed,分别对应uint和int solidity也支持数学运算,此时如果integer加上数学运算,就一定会发生overflow和underflow。 比如这个: function g(uint a, uint b) pure public returns (uint) { return a - b; } 我输入(2,3)会发生什么? 如果我想要(2 - 3),得到结果值为最大的正整数的结果(即 type(uint128).max),那我要怎么写代码? function g(uint a, uint b) pure public returns (uint) {
unchecked {
return a - b;
}
} unchecked { ... }是一个语句块,其作用是暂时屏蔽overflow/underflow, 产生一个wrap的效果 客制化错误,save gas消耗如下代码:当执行出错时,换用不同的报错,会导致gas费用不一样 ...
function withdraw() public {
if (msg.sender != owner) {
revert("error")
}
... revert() 接受一个字串当作error message,此时调用withdraw报错消耗gas费用23645 或者换用定制错误的方式: // SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
error Unauthorized();
contract VendingMachine {
address owner;
function withdraw() public {
if (msg.sender != owner) {
revert Unauthorized();
}
... revert 后接客制化错误,此时不要加(), // 23645 // 23663 // 23594 // 23391 最后一个不得不提的assert,也跟错误检测有关。 合约创建我们想创建合约,需要书写*.sol,然后solc编译,然后部署合约到链上,...(得到合约的链地址,再构建交易来与合约互操作) 其实合约中也可以再创建另一个合约,见如下的代码: ...
contract D {
uint public x;
constructor(uint a) { x = a; } //注意这还有错误
}
contract C {
// C合约创建时就自动创建了合约D
D d = new D(4);
// 另一种创建合约D的方式
function createD(uint arg) public returns(D) {
D newD = new D(arg);
return newD;
}
// 另一种创建合约D的方式,同时并更新新建合约的余额
function createAndEndowD(uint arg, uint amount) public returns(D) {
D newD = new D{value: amount}(arg);
return newD;
}
... 这边编译会报错: 编译,部署,给合约账户充值(不然合约无法执行创建CA账户D的操作),然后触发合约createD 或者 createAndEndowD来让我们的合约再创建一个新合约账户; 交易,新账户的地址等信息都可以在Etherscan的交易页面Overview和Internal Txns中可以看到。 新合约的地址是如何产生的? 及 create2上面已经讲了合约创建的过程,但这个是在合约编程层面;在EVM层面,有对应的操作码:
我们通常的合约创建,其合约地址就是透过create(v, p, n)来创建的,准确来说是 为了能加强合约地址的predictable,Solidity和EVM都添加了create2来支持,其生成参数去掉了nonce,加入了其他数据。 function createDSalted(bytes32 salt, uint arg) public {
// This complicated expression just tells you how the address
// can be pre-computed. It is just there for illustration.
// You actually only need ``new D{salt: salt}(arg)``.
address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
keccak256(abi.encodePacked(
type(D).creationCode,
abi.encode(arg)
))
)))));
D d = new D{salt: salt}(arg);
require(address(d) == predictedAddress);
} 可以看到,create2来生成合约地址的四个要素:0xff, 发起合约创建的账户地址,salt,合约的字节码(哈希)
实际上,Uniswap也是利用了create2,当任一用户指定任意两种token,总是能找到对应的合约地址(如果之前这两种token已经在池子里面建立了交易对合约的话)
一个"可升级的"智能合约。之前谈到,以太坊上的合约是一次性部署的;如果需要修改合约(比如说合约有bug),我们就需要重新部署新合约;但有时这给应用带来了麻烦,此时我们希望该合约的新版本还部署在原来的地址上。 |
气死了,忘记保存,不小心按下电源键,半天写的东西都没了 |
Solidity语法数值类型:
数值类型bool,int256, uint256, address, address payable, bytes1, bytes2...bytes32, enum 特别说一点,类型之间可以相互转换,转换的语法是:T(S) ,S是原类型,T为要转换的类型,比如: address a = address(0x123);
payable p = payable(a); 有时候忘了这一点,还以为payable()是一个特别的函数(我看stackoverflow上有人还问) 值类型的意思是,当发生赋值操作时,如: bytes20 b
bytes20 c = b; 此时b和c都会指向各自的一个定长20的字节(数组), 两者地址不一样,虽然内容是一样的。 详细的语法解释,参见文档 reference引用类型Array/数组:有固定长度的,也有变长的,如:uint8[6] a, uint32[] b, 或者address[] wallet_addr; 所有的引用类型变量,在声明时,都需要特别指定
Storage // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract CodiesAlert {
string twitterAccount = "Anni_Maan";
function displayAccount() public view {
string storage Newvar = twitterAccount;
}
} 此时twitterAccount和Newvar都是storage,存储于链上。 Memory // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract CodiesAlert {
string twitterAccount = "Anni_Maan";
function displayAccount() public view returns(string memory) {
string memory Newvar = "1024";
return Newvar;
}
} Calldata 以上这些只是从syntax角度来讲,但实际这些语法的解释,在编译后的字节码和在EVM上的实现才能真正衡量数据类型所占用资源;而这也才真正决定着这些变量所对应的gas费用,参见: 当变量之间相互赋值时,有时会发生值copy的情况,有时会只复制引用,规则见下:
function deposite() {
msg.sender.transfer(msg.value);
} msg.sender就是EOA账户,我们转1个Ether到该合约账户上,msg.value就是10000000000000000, transfer()执行转账操作。 实际上,为了更好处理用户调用调用合约中的函数,并附上转账操作,以太坊还单独设计了receive()和 fallback() --- 上面有列,但没有讲。 receive() external payable {} // 是不是特别简单? 这样一行就完成了合约充值的功能 receive和fallback的选择,如下是选择图:
我们实际在编写合约代码时,如果没有加receive或者fallback,SOL编译器会提示我们加;但如果我们执意不加入,也没有设计函数接口来接收外部账户的转账,那么这个合约部署到链上,就是一个死合同,什么都做不了;此时我们使用EOA账户给该合约账户转Ether,转账交易也会失败。 |
函数修饰符 function modifier该功能类似Python中的函数修饰器作用,添加额外检查功能或者。。。,见例子: // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
contract owned {
constructor() { owner = payable(msg.sender); }
address payable owner;
// This contract only defines a modifier but does not use
// it: it will be used in derived contracts.
// The function body is inserted where the special symbol
// `_;` in the definition of a modifier appears.
// This means that if the owner calls this function, the
// function is executed and otherwise, an exception is
// thrown.
modifier onlyOwner {
require(
msg.sender == owner,
"Only owner can call this function."
);
_;
}
}
contract destructible is owned {
// This contract inherits the `onlyOwner` modifier from
// `owned` and applies it to the `destroy` function, which
// causes that calls to `destroy` only have an effect if
// they are made by the stored owner.
function destroy() public onlyOwner {
selfdestruct(owner);
}
} onlyOwner 就是函数修饰器,之前文章往往在destroy函数中都要检查msg.sender是否是owner,即创建合约时的账户;现在换成用function modifier,对应的逻辑移到了onlyOwner中,想要此功能的函数只需要加上onlyOwner修饰器即可,前提条件是:
function modifier的定义就跟普通函数一样,只是把function关键词换成modifier;不过modifier还是有其自己的特性:
contract priced {
// Modifiers can receive arguments:
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
contract Register is priced {
mapping (address => bool) registeredAddresses;
uint price;
constructor(uint initialPrice) { price = initialPrice; }
// It is important to also provide the
// `payable` keyword here, otherwise the function will
// automatically reject all Ether sent to it.
function register() public payable costs(price) {
registeredAddresses[msg.sender] = true;
}
} 另外推荐Solidity库: OpenZeppelin,其中也有很多修饰器的编写。 |
合约的单元测试 contract unit test仔细想想一下,如果想要做单元测试,比如想测试一下如下合约中的排序:(省去不相关的代码部分) contract Sort {
uint[] public numbers;
constructor() {
numbers = new uint[](4);
numbers[0] = 2;
numbers[1] = 1;
numbers[2] = 4;
numbers[3] = 3;
}
function sort() external {
uint i = 0;
uint left = 0;
uint len = numbers.length;
uint tmp;
while (left < (len - 1)) {
for (i = left; i < len; i++) {
if (numbers[i] < numbers[left]) {
tmp = numbers[i];
numbers[i] = numbers[left];
numbers[left] = tmp;
}
}
left += 1;
}
}
...
} 假设待排序的numbers数据是Storage location,sort方法来对numbers中的数据进行排序。 首先是我们不可能真的用过去那种函数调用运行(进程,函数)的方式来进行,一定是在“合约,部署,消息(调用), DApp收到返回结果,检查”这样的场景下进行。 说实话,如果全手动来构建这一切,太麻烦了。目前有两个框架工具供我们选择:Remix Solidity Unit test和Hardhat test
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
// This import is automatically injected by Remix
import "remix_tests.sol";
// This import is required to use custom transaction context
// Although it may fail compilation in 'Solidity Compiler' plugin
// But it will work fine in 'Solidity Unit Testing' plugin
import "remix_accounts.sol";
import "../contracts/VenderMachine.sol";
// File name has to end with '_test.sol', this file can contain more than one testSuite contracts
contract testSuite {
VenderMachine vm;
function beforeAll() public {
vm = new VenderMachine();
vm.sort();
}
function checkSort() public {
Assert.equal(vm.getNumber(0), uint(1), "numbers[0] is not 1");
Assert.equal(vm.getNumber(1), uint(2), "numbers[1] is not 2");
Assert.equal(vm.getNumber(2), uint(3), "numbers[2] is not 3");
Assert.equal(vm.getNumber(3), uint(4), "numbers[3] is not 4");
}
} 我个人感觉用这个框架,优点是本身集成在Remix IDE中,有方便的generate testcase的按钮(然后用户可以按照需求,在此基础代码上修改)
如下是Hardhat的针对sort的测试代码: const {expect} = require("chai");
const hre = require("hardhat");
const {loadFixture} = require("@nomicfoundation/hardhat-network-helpers");
describe("MyContract", function () {
async function deploy() {
// Contracts are deployed using the first signer/account by default
const [owner] = await hre.ethers.getSigners();
const contract = await hre.ethers.getContractFactory("MyContract");
const faucet = await contract.deploy();
return {faucet};
}
describe("sort", function () {
it("sort", async function () {
const {faucet} = await loadFixture(deploy); //创建和部署合约
faucet.sort(); //调用合约sort方法
for (let i = 0; i < 4; i++) { //做结果判断
expect(await faucet.getNumber(i)).to.equal(i + 1);
}
})
})
}); 个人建议Unit test用Hardhat的方案来做比较好。 |
以太坊的区块,交易,bird viewblock区块数据结构:
区块本身不包含区块内交易的原始信息,通过transaction hash root的方式,可以索引到属于该区块的交易,[参见] 交易的数据结构:
签名数据中不仅可得到针对该交易的签名数据,也可以得到交易发送者的(以太坊)地址(这就是为什么交易数据结构没有包含类似 签名
如下的javascript代码,展示对消息"hello",使用小狐狸metaMask,生成签名的过程:
运行结果如下: 如下代码是从签名中导出(recover)metaMask钱包地址的函数: // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Verify {
function VerifyMessage(bytes32 _hashedMessage, uint8 _v, bytes32 _r, bytes32 _s) public pure returns (address) {
bytes memory prefix = "\x19Ethereum Signed Message:\n32";
bytes32 prefixedHashMessage = keccak256(abi.encodePacked(prefix, _hashedMessage));
address signer = ecrecover(prefixedHashMessage, _v, _r, _s);
return signer;
}
} 有一点特别的是,signMessage()函数中在调用metaMask签名时,使用了"personal_sign" method,所以后续在恢复地址 |
为什么要用_msgSender(),而不用msg.sender?前面的例子中我们都毫无例外的使用了msg.sender作为交易的发起人,这看起来很直观,没有什么问题。 这都和OpenZeppelin提供的合约库,和相关的EIP-1077,GSN相关。 如果我们可以免费发送交易,是不是会更好?-- 获取新用户本来就要花一笔钱。 总之,在这个思路上,很多人做了尝试,比如Meta Transaction 简单一点来说,这里面有三个角色:
要特别提两点:
作为Recipient合约的书写者, 在OpenZeppelin支持GSN并把合约库加入了GSN功能后,如果我们在合约中使用msg.sender,并以为它一定是 Sender, 这种假设在GSN网络中就是错误的;在GSN的场景下,Recipient合约中msg.sender会是Relay Hub。 出于这个原因,如果想要获取sender地址, OpenZeppelin提供了_msgSender()来让我们获取sender地址,代码: function _msgSender() internal view virtual override returns (address payable) {
if (msg.sender != getHubAddr()) {
return msg.sender;
} else {
return _getRelayedCallSender();
}
} 总结:
|
推荐一个好的VS code的solidity的插件 |
Solodity Library就像std库 for C++,rust,solidity也支持库,作用一方面是提供已有现成的,更好的某功能的实现,另一个带来的结果是让用户编写的合约,总体大小变小了,gas费也低了, Solidity的库也是部署要当作合约部署在链上,不过库的代码跟合约,在如下方面不一样:
这些限制都比较符合”直觉“ 另外,合约在调用库时,EVM在底层实现的时候,会使用一个叫 库虽然不能包含状态变量,但caller 合约可以把自己的状态变量当成函数参数,传入库的函数中,此时库函数对状态变量的修改也保存在caller合约这边。 使用库的方式:
import {MathLib} from './lib-file.sol';
using MathLib for uint; //MathLib提供函数subUint(uint x, uint y)
uint a = 10;
uint b= 10;
uint c = a.subUint(b); |
我们还有其他选择吗智能合约开发语言,目前就属Solidity拥有最多用户,而与之对应的链是Ethereum。 在以太坊生态中,除了Solidity,剩下的还有Vyper,但我看了两者数量对比后,..., sorry。 其他的链,比如Polkadot,NEAR,Solana,首先是都有原生的开发合约的语言,Rust,和对应的SDK;那有如下两个问题:
其实涉及到底层的VM,虚拟机了; Solidity只是编程语言,使用solc(Solidity配套的编译器),编译出来的结果是可以跑在EVM(Ethereum Virtual Machine)上的字节码。 我们设想一下,如果现在所有的公链都提供了兼容的EVM,那么我们用Solidity编写的合约,或Rust+SDK的编写合约,可以 我个人认为,合约和虚拟机不可能在各个公链中复用,就拿以太坊来说,合约设计到以太坊的gas费,状态变量的存储,账户的设计(solana的账户设计就跟以太坊不太一样,更不要说Solana的PDA),更不要说各个公链对虚拟机图灵完备性的不同选择等等。 真实的情况是,Polkdot,Solana,Near都有自己的虚拟机,基于WASM; 而针对EVM,每一家也都提供了各种解决方案。
未来WASM虚拟机规范,会成为未来的 总结
|
Ethereum Token Standards 以太坊代币标准
|
非常不错的以太坊学习资料: |
以太坊交易的gas用量Gas用量代表了该笔交易所涉及的所有操作,换算为一个统一的中间单位来衡量,即gas用量(gas usage by tx) 个人做过测试,发现一个情况,如下场景: 经过分析,这涉及到EVM的操作和优化;在每一次的交易,可以看到:
当VM operation steps一样的时候,两笔交易的gas用量必然一样。 PS: 做什么事情都需要gas费?合约的函数调用,有时候是不需要生成交易:
但根源并不是本身这些函数不消耗gas,实际上,我们调用一个合约的函数,该函数内部先使用transfer实现一个转账(代表一定会产生交易),然后再调用view或者pure的合约函数,整个过程算下来,这些view/pure函数仍然对应着EVM中的操作指令,这些都要计算为gas消耗。 |
Oracle for Blockchain之前了解的预言机,是因为:
提供数据但从大的角度来讲,预言机是“链上” - “链下”的中间连接,能(高质量的)满足这个功能都可以称之为预言机
出于设计考虑,预言机网络与链网络分开(Filecoin的DRand network,Chainlink network) 我觉得预言机还需要提供如下特性:
链下计算/计算预言机或者称之为
我也感到了这和Layer2有相似之处。 参考: |
Solidity的基类/子类,继承,接口,抽象类,重载Solidity支持这些,都是从Python学来的。 到目前为止,建立使用接口,基类,子类继承的方法来构建合约,实际上这也是OpenZeppelin这边的用法,比如ERC-20的实现:
contract ERC20 is Context, IERC20, IERC20Metadata {
...
function name() public view virtual override returns (string memory) { return _name; }
...
funtion transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount); //以_开头的都是internal函数
_transfer(from, to, amount());
return true;
}
}
目前Solidity的关于这部分的语法规则:
考虑到多重继承的复杂性和审计代码难度,我个人觉得:
现代OOP设计里面,我觉得,应该多用组合,少用继承。 |
EVM
参考: |
Meta Transaction and EIP712Meta transaction又叫gasless transaction,解决用户发送交易但账户没有Ether支付交易费的场景。 操作流程:
这个解决方案就是为了能用户更容易使用交易(onboarding)。Relay在这个场景中,要么与用户用其他方式结算(比如用户购买充值卡)或者Relay把费用当作是 为支持这个场景,dAPP需要支持(sign message,MetaMask等钱包都已支持),Relayer需要提供支持,最后smart contract也要提供支持才可以。 EIP712Wallet软件在签署消息时,UI上提示的都是Hex String,确认要签的消息/交易几乎不可能。 EIP提出了规范,以typed structure data and siging来规范消息的形式,wallet UI显示清楚地显示要签署的交易的内容。 smart contract code handle如下是UniswapV2在合约处的代码,permit()处添加了meta transaction的支持,提供了ERC20的approve的功能 function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s); // v, r, s 就是签名中的三要素,ecrecover()拿到用户公钥
require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
_approve(owner, spender, value);
} DOMAIN_SEPARATOR 和 PERMIT_TYPEHASH (permit函数原型信息hash + 自定义信息)的定义在这里: DOMAIN_SEPARATOR = keccak256(abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)), //name为'Uniswap V2'
keccak256(bytes('1')),
chainId, //chainId为所处以太坊chainid
address(this)
));
bytes32 public constant PERMIT_TYPEHASH
= keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); 最开始可能会这个格式有疑问,这个在EIP712中有规定: Note: dApp组装消息时,也要对应这个格式来操作。 |
solidity中有小数吗?准确来说,的确是有的。主要是因为Solidity中有 整数符号整数和无符号整数,即int256, uint256;这种类型是EVM中最最基本的,在栈上,mapping中,操作码的指针,都是该类型;由于太基本,也缩写为int,uint, 英文也有叫“Word”,熟悉就好,对得上就行。 整数字面量可以用 uint a = 0x10_000;
int b = 12_345; 整数在做除法运算时,做整除处理,如: uint a = 5;
uint b = 7;
uint c = a /b; // c的结果为 0 (字面量)小数小数是俗称,实际上应该分成:定点数和浮点数,定点数是类似3.5这种的,solidity中支持的浮点数类似2e10, 2e-10类型的。 uint a = 1;
uint c = 2.345e2;
uint d = 2.5 + 0.5 + a; // ok!
uint e = 2.5 + a + 0.5; // error! 但solidity中小数只能是字面量,如果赋值给一个变量,就像上面的uint c = 2.345e2, 此时会把2.345e2的值,234.5,截断小数点后的整数值234,赋给c。 不过字面量,特别是字面量小数,的确用处没有那么大,在通常的合约编程中,运算总少不了变量,有变量赋值就会发生“小数点后的被截断”,所以从某种角度说Solidity不支持小数也没错。 |
Foundry |
Metamask小狐狸钱包签名这件事slowmist.medium.com/slow-mist-blank-check-eth-sign-phishing-analysis-741115bd0b1f(漫雾写的关于盲签的钓鱼事件的分析) Metamask 签名方法因为小狐狸钱包用的最多,所以以它家为例子进行分析。
RPC跟签名相关的RPC函数就是两个: eth_sign(addr, message) -> Signature
签名实际上是对交易内容hash(keccak256)之后的结果,即hash值做签名,格式如下: signTransaction(object, from, to, gas, gasPrice, value, data, nonce) -> SignedData返回的SignedData可以当作raw data被sendRawTransaction()发送 link: ethereum.org/en/developers/docs/apis/json-rpc 钱包APIeth_sign最早出现的,但从现在来看,非常不安全的,已经是要被淘汰的接口 跟后面的personal_sign一样,都是会调用RPC的eth_sign; 通常的攻击方法为:
personal_sign跟eth_sign类似,但最大的不同是,personal_sign在metamask的处理流程为先在钱包UI上展示交易内容,待用户选择了签署按钮后,再进行hash和调用sign的动作 signTypedData Vx实现EIP-712,即结构化的交易内容展示,最主要可以解决:
参见:github.com//issues/28#issuecomment-1254550216 link: |
签名和安全方面,想深入了解,就脱离不了钱包软件,当前就是MetaMask最广泛了。 这里的连接,是一个模拟,metamask.github.io/test-dapp/ |
Solana合约验证问题在网络上有看到 我们无法验证网站的宣传是否属实,因为Solana上的合约,到现在也没有一个好方法来验证。 通常验证合约的流程为:
以太坊上合约有成熟可靠的方法来验证合约,如果要设计一个提供验证服务的网站,则服务可以设计为如下流程:
但Solana上,目前没有一个切实可行的方法来验证合约。
我在这里讲述一下第3点“Solidity合约编译”。合约验证中最重要的就是保证“确定性”,即给定合约源代码(代码中还可能包含其他的库),能否保证编译后的二进制文件,与给定合约源代码存在确定性的一一映射,也就是说:
Ethereum上Solidity合约编译就可以保证确定性: Solana合约编译环境所依赖的Crates,Anchor都有版本,版本信息保存在Cargo.toml中,除此之外,还有其他的文件会影响到编译过程,如toolchain.toml,和Rust编译版本号选择机制等等都存在非确定性的因素。 在Reddit上有网友讨论,也有开发者在Solana github上提过类似的问题:
Solana上目前较为可行的一种验证方法(WormHole),如果设计为一个网络服务的话,大致的设计如下:
最后因为我个人之前接触过POW,我认为,如果合约无法验证,就做不到 “Don't Trust It,Verify IT!” |
all things about smartcontract, not limited to Solidity of Ethereum
The text was updated successfully, but these errors were encountered: