So one of the extra flavors we want to add to the EVM for CP is being able to send/receive CP assets.
There’s 3 possible aproaches to take and I’d like to discuss them and get ideas from other devs on this subject.
as an example contract we’ll take a crowdfund that is VERY stripped down and doesn’t adhere to any best practices, if the goal is reached the person who did the last contribution receives all of it xD
XCP
contract crowdfund {
uint contributed = 0; // tracks status
uint goal = 1000; // goal for payout
function donate() {
contributed += msg.value; // increase contributed
// check if we reached the goal
if (contributed >= goal) {
msg.sender.send(contributed); // payout to the last `sender, lucky bastard
}
}
}
the important part here is msg.value
which is the amount of XCP send (excluding gas, those 2 are seperated).
now when compiled msg.value
is translated into the CALLVALUE
opcode, which ‘returns’ (puts it on the stack) the value.
Option 1
So option 1 is; add a msg.asset
which is the asset name of what was send and we keep using msg.value
for the value,
so if this contract would be about a crowdfund using GOLDTOKEN
it would be like this:
contract crowdfund {
uint contributed = 0; // tracks status
uint goal = 1000; // goal for payout
function donate() {
if (msg.asset != "GOLDTOKEN") {
throw; // throw will 'undo' anything that happened, so the sender get's his assets back
}
contributed += msg.value; // increase contributed
// check if we reached the goal
if (contributed >= goal) {
msg.sender.send(contributed, "GOLDTOKEN"); // payout to the last sender, lucky bastard
}
}
}
the important part here is msg.value
now is either amount of XCP send or if msg.asset
is not null it’s the amount of that asset send.
now when compiled msg.asset
would be translated into the CALLASSET
opcode, which ‘returns’ (puts it on the stack) the name of the asset.
I think this is by far the worst option, because every contract that is expecting XCP needs to check msg.asset == null
or msg.asset == "XCP"
, any contract copied from Ethereum you could cheat by sending them some token and if it doesn’t have that check … oops.
I wanted to put the option in though for spelling out each option that I considered!
Option 2
So option 2 is; add a msg.asset
and a msg.assetvalue
which are the asset name of what was send and the amount of that asset send,
so if this contract would be about a crowdfund using GOLDTOKEN
it would be like this:
contract crowdfund {
uint contributed = 0; // tracks status
uint goal = 1000; // goal for payout
function donate() {
if (msg.asset != "GOLDTOKEN") {
throw; // throw will 'undo' anything that happened, so the sender get's his assets back
}
contributed += msg.assetvalue; // increase contributed
// check if we reached the goal
if (contributed >= goal) {
msg.sender.sendasset(contributed, "GOLDTOKEN"); // payout to the last sender, lucky bastard
}
}
}
the important part here is msg.asset
is the asset that was send, msg.assetvalue
is the amount of that asset send.
now when compiled msg.asset
would be translated into the CALLASSET
opcode, which ‘returns’ (puts it on the stack) the name of the asset.
and msg.assetvalue
would be translated into the CALLASSETVALUE
opcode, which ‘returns’ (puts it on the stack) the amount of that asset send.
now this option isn’t bad, it’s explicit and it keeps proper backward compatability with Ethereum original contracts.
It does require adding 2 new opcodes, which isn’t a very big deal except that we have to pick a 1 byte number for those opcode,
the EVM already has ~130 opcodes and anything below 160 has been exhausted more or less because they sometimes skip a few numbers (the only reason being for easy ‘reading’).
There’s no guarentee that a number we pick won’t eventually be taken by any new features on the EVM and if we want to stay of to date and compatible with the original EVM that will conflict …
We could take the OP_NOP aproach of bitcoin and use a OP_PUSH
followed by a sequence we deem unique enough OP_PUSH9 0x43 0x4e 0x54 0x52 0x50 0x52 0x54 0x59 <OUR_OPS_HERE>
(that’s the HEX of CNTRPRTY, the same prefix we use in bitcoin transactions), but that eats a lot of data.
if we’d change the number we’ve assigned to our own opcode, whenever a new opcode is added to the original EVM that conflicts, then that could result in really awkward bugs so that’s a no go.
Or we could ask the EVM devs to reserve 1 opcode for and use 2 bytes for our opcodes, so if we’d get them to reserve 0xfe
for us / others who fork the EVM then our opcodes would be 0xfe 0x01
, 0xfe 0x02
, etc.
Option 3
The EVM has a bunch of hardcoded / precompiled contracts:
- ‘0000000000000000000000000000000000000001’: ecrecover
- ‘0000000000000000000000000000000000000002’: sha256
- ‘0000000000000000000000000000000000000003’: ripemd160
eg; the solidity compiler will compile sha256(DATA)
to a CALL
to contract 0000000000000000000000000000000000000002
with the DATA
, or ecrecover(H, V, S, R)
to a CALL
to 00..01
with the data being H+V+S+R
.
we can easily add more to these, eg;
- ‘434e545250525459000000000000000000000001’: sendasset # CNTRPRTY prefix in hex
- ‘434e545250525459000000000000000000000002’: assetreceived # CNTRPRTY prefix in hex
so if this contract would be about a crowdfund using GOLDTOKEN
it would be like this:
contract crowdfund {
uint contributed = 0; // tracks status
uint goal = 1000; // goal for payout
function donate() {
var (assetreceived, assetvalue) = assetreceived();
if (assetreceived != "GOLDTOKEN") {
throw; // throw will 'undo' anything that happened, so the sender get's his assets back
}
contributed += assetvalue; // increase contributed
// check if we reached the goal
if (contributed >= goal) {
sendasset(msg.sender, contributed, "GOLDTOKEN"); // payout to the last sender, lucky bastard
}
}
}
the important part here is assetreceived
that returns the asset that was send and the amount of that asset send.
now when compiled assetreceived()
would be translated into the CALL 434e545250525459000000000000000000000002
which ‘returns’ (puts it on the stack) the name of the asset and the amount send.
and sendasset
becomes CALL 434e545250525459000000000000000000000001 <ADDRESS> <VALUE> <ASSET>
.
this option requires the least amount of change, but it also feels the least ‘native’.